方式一
从类继承
创建一个继承该类的子类,并重写该类的run() --> 在run()中声明该线程执行的操作。 创建该类的子类的对象 4. 通过该对象调用start()。 例子:遍历100以内的所有偶数
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
// t1.start();
//我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}
复制
方式2
实现接口
创建一个实现接口的类来实现类中的抽象方法:run()创建一个实现类的对象,并将这个对象作为参数传递给该类的构造函数。 创建该类的对象并通过该类的对象调用start()。 比较两种创建线程的方法。 正在开发: 优先级:接口实现 原因: 1、该实现方法没有类单继承的限制 2、该实现方法更适合处理多线程共享数据的情况。 联系:类 相似之处:这两个方法都需要重写run(),并在run()中声明线程要执行的逻辑。
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2. 实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3. 创建实现类的对象
MThread mThread = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
复制
方式3
实现接口。
— JDK 5.0新增:如何理解通过实现接口创建多线程比通过实现接口创建多线程更强大? 1. call()可以有返回值。 2. call() 可以抛出异常,并被外部操作捕获,获取异常信息 3. 支持泛型
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
复制
方式4
使用线程池
好处: 1.提高响应速度(减少创建新线程的时间) 2.减少资源消耗(复用线程池中的线程,不用每次都创建) 3.方便线程管理:核心池大小:最大数量 : 当线程没有任务时,会在最大时间长度后终止**
面试题:创建多线程有多少种方式?四种
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
复制
2、线程安全导致线程安全(卖票例子)
示例:创建三个窗口售票,总票数为100张。使用实现接口的方法
1、问题:售票过程中出现重复票、错票-->出现线程安全问题。 2、问题原因:当一个线程操作票据并且操作尚未完成时,其他线程参与。 也进来操作一下票吧。 3、如何解决:当一个线程a正在操作时,其他线程无法参与。 直到线程a完成操作后,其他线程才能开始操作。 这种情况下,即使线程a被阻塞,也无法改变。
解决方案
在Java中,我们使用同步机制来解决线程安全问题。
方法一
在Java中,我们使用同步机制来解决线程安全问题。 方法一:同步代码块(同步监视器){ //需要同步的代码} 说明:1、操作共享数据的代码就是需要同步的代码。 -->不能包含太多代码,也不能包含太少代码。 2.共享数据:多个线程共同操作的变量。 例如:它正在共享数据。 3.同步监视器,俗称:锁。 任何类的对象都可以充当锁。 要求:多个线程必须共享同一个锁。 补充:在实现创建多线程接口的方式中,我们可以考虑用这个来充当同步监视器。
class Window1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
// Dog dog = new Dog();
@Override
public void run() {
// Object obj = new Object();
while(true){
synchronized (this){//此时的this:唯一的Window1的对象 //方式二:synchronized (dog) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dog{
}
复制
方法2
同步方法。 如果操作共享数据的代码完全声明在一个方法中,我们不妨声明这个方法是同步的。 同步方法解决了线程安全问题。 ——好处 操作同步代码时,只有一个线程可以参与,其他线程等待。 相当于单线程进程,效率较低。 -局限性
使用方法解决实现接口时的线程安全问题方法总结: 1、方法仍然涉及同步监视器,但我们不需要显式声明它们。 2、对于非静态同步方法,同步监视器是:this。 对于静态同步方法,同步监视器是:当前类本身。
class Window2 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while(true){
//正确的
// synchronized (obj){
synchronized (Window2.class){//Class clazz = Window2.class,Window2.class只会加载一次
//错误的方式:this代表着t1,t2,t3三个对象
// synchronized (this){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
复制