Java多线程
编辑时间:2021/03/14
读完本节:大概花费50分钟,共5833词
文章目录
1.线程的分类
- 在Java中线程分为两类:一种是守护线程,一种是用户线程.
- 两种线程在每个方面几乎都是相同的,唯一的区别是判断JVM何时离开,
- 守护线程是用雷服务用户线程的,通过start()方法前调用Thread.setDeamon(true)可以把一个用户线程变成一个守护线程
- Java垃圾回收就是一个典型的守护线程
- 若JVM中都是守护线程,当前JVM将退出
2.线程的生命周期
-
JDK中用Thread.State类定义了线程的集中状态想要实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程 ,在它的一个完整的生命周期中通常要经历如下五种状态:新建、就绪、运行、阻塞、死亡。
-
新建:当一个Thread类或其子类的对象被声明或创建时,产生新的线程对象处于新建状态
-
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
-
运行:当就绪的线程被调度并获得CPU的资源时,便进入运行状态,run()方法定义了线程的操作和功能
-
阻塞:在某种特殊的情况下,被人为的挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入线程阻塞状态
-
死亡:线程完成了它的全部工作或线程被提前强制性的终止或出现异常导致结束
-
图示:
3.线程的同步
-
在Java中,通过同步机制,来解决线程安全的问题
-
线程同步机制的方式一:同步代码块
-
格式
synchronized(同步监视器){
//需要被同步的代码
}
-
操作共享数据的代码即为需要被同步的代码;
共享数据:多个线程都可进行操作的变量
同步监视器常称为锁,任何一个类的对象都可以充当锁,多线程必须使用同一把锁,即使用同一个类的对象
-
在实现Runnable接口创建多线程的方式中可以考虑使用this充当同步监视器
-
使用举例:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式实现
分析:在不使用线程同步的卖票的过程中,出现了重票、错票的线程安全问题。
问题出现的原因:当某个线程操作车票的过程中,尚未完成本次操作,其他线程参与进来,也对车票进行操作。
解决方法:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完成ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变。
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 17:56 * @Description: 对存在线程不安全的问题进行线程同步的改进, * 使用同步代码块完成线程同步,使用实现Runnable的方式创建线程 */ class Window implements Runnable{ private int ticket = 100; private Object object = new Object(); @Override public void run() { while(true){ //这里的synchronized中的object可以考虑使用this代替 //this指代当前对象,此处只造了一个Window对象, //因此this指代的对象仍然是唯一的,因此不违反使用同一把锁的原则 synchronized(object){ if(ticket > 0){ System.out.println(Thread.currentThread().getName()+ ":" + ticket); ticket--; }else{ break; } } } } } public class TicketTest { public static void main(String[] args) { TicketTest test = new TicketTest(); Window window = new Window(); Thread thread1 = new Thread(window); Thread thread2 = new Thread(window); Thread thread3 = new Thread(window); thread1.setName("窗口1"); thread2.setName("窗口2"); thread3.setName("窗口3"); thread1.start(); thread2.start(); thread3.start(); } }
使用继承Thread的方式实现三个窗口卖票,并且完成线程同步的要求
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 18:15 * @Description: 对存在线程不安全的问题进行线程同步的改进, * 使用同步代码块完成线程同步,使用子类继承Thread的方式创建线程 */ class Window1 extends Thread{ private static int ticket = 100; private static Object object = new Object(); /** 对父类中的run方法进行重写 */ @Override public void run() { while(true){ //错误的方式,此时的object或者this是thread1、thread2、thread3 //意味着这个加上的锁不唯一,不能成功的实现线程同步 // synchronized(this){ //正确的方式 // synchronized(object){ synchronized(Window1.class){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } } } } public class TicketTest1 { public static void main(String[] args) { Window1 window1 = new Window1(); Window1 window2 = new Window1(); Window1 window3 = new Window1(); window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); window1.start(); window2.start(); window3.start(); } }
-
-
线程同步机制的方式二:同步方法
-
如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步的。
-
同步方法仍然涉及到同步监视器,只是不需要显示声明
-
非静态的同步方法,同步监视器是:this;静态的同步方法,同步监视器是:当前类本身
-
使用举例:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式实现
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 18:44 * @Description: 对存在线程不安全的问题进行线程同步的改进, * 使用同步方法块完成线程同步,使用实现Runnable的方式创建线程 */ class Window2 implements Runnable{ private int ticket = 100; /** 实现Runnable接口中的run()方法 */ @Override public void run() { while(true){ show(); if(ticket == 0){ break; } } } /** run方法中的show方法,使用同步方法的方式实现线程同步,此时的同步监视器是this */ private synchronized void show(){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; } } } public class TicketTest2 { public static void main(String[] args) { Window2 window = new Window2(); Thread thread1 = new Thread(window); Thread thread2 = new Thread(window); Thread thread3 = new Thread(window); thread1.setName("窗口1"); thread2.setName("窗口2"); thread3.setName("窗口3"); thread1.start(); thread2.start(); thread3.start(); } }
创建三个窗口卖票,总票数为100张,使用子类继承Thread的方式实现
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 18:58 * @Description: 对存在线程不安全的问题进行线程同步的改进, * 使用同步方法块完成线程同步,使用子类继承Thread的方式创建线程 */ class Window3 extends Thread{ private static int ticket = 100; /** 重写父类中的run()方法 */ @Override public void run() { while(true){ show(); if(ticket == 0){ break; } } } //此时的show方法中的同步监视器是:thread1、thread2、thread3 // private synchronized void show() { private static synchronized void show(){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; } } } public class TicketTest3 { public static void main(String[] args) { Window3 window1 = new Window3(); Window3 window2 = new Window3(); Window3 window3 = new Window3(); window1.setName("窗口1"); window2.setName("窗口1"); window3.setName("窗口1"); window1.start(); window2.start(); window3.start(); } }
-
-
线程同步机制的方式三:Lock(锁)(JDK5.0后)
-
JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
**java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。**锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象进行加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,较常用的是ReentrantLock,可以显示加锁、释放锁。
-
创建三个窗口卖票,总票数为100张,使用Runnable接口的方式实现
import java.util.concurrent.locks.ReentrantLock; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 20:18 * @Description: 使用Lock(锁)实现线程同步, * 使用实现Runnable接口的方式创建线程 */ class Window4 implements Runnable{ private int ticket = 100; //1. 实例化ReentrantLock,实现它的对象锁 private ReentrantLock lock = new ReentrantLock(); @Override public void run() { try{ //2. 调用Lock() lock.lock(); while(true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } }finally { //3. 调用解锁方法:unlock lock.unlock(); } } } public class TicketTestLock { public static void main(String[] args) { Window4 window = new Window4(); Thread thread1 = new Thread(window); Thread thread2 = new Thread(window); Thread thread3 = new Thread(window); thread1.setName("窗口1"); thread2.setName("窗口2"); thread3.setName("窗口3"); thread1.start(); thread2.start(); thread3.start(); } }
创建三个窗口卖票,总票数为100张,使用子类继承Thread的方式实现
import java.util.concurrent.locks.ReentrantLock; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 20:29 * @Description: 使用Lock(锁)实现线程同步, * 使用子类继承Thread的方式创建线程 */ class Window5 extends Thread{ private static int ticket = 100; //1. 实例化ReentrantLock,实现它的类锁 private static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try{ while(true){ //2. 调用lock方法 lock.lock(); if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } }finally { lock.unlock(); } } } public class TicketTestLock1 { public static void main(String[] args) { Window5 window1 = new Window5(); Window5 window2 = new Window5(); Window5 window3 = new Window5(); window1.setName("窗口1"); window2.setName("窗口2"); window3.setName("窗口3"); window1.start(); window2.start(); window3.start(); } }
-
-
synchronized与Lock的异同:
- 相同点:二者都可以解决线程安全问题
- 不同点:
- synchronized是java的关键字,Lock是接口
- synchronized是隐式锁出了作用域自动释放或者出现异常自动释放,即synchronized机制在执行完相应的同步代码以后,自动释放同步监视器;Lock是显示锁,Lock需要手动启动同步(Lock()),同时结束同步也需要手动实现(unlock())
- synchronized不能获取锁的状态,Lock可以获取锁的状态
- synchronized是非公平锁,lock可以是公平锁(先进先出)也可以是非公平锁(由CPU的分配资源决定)
- Lock只有代码块锁;synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费教授的时间来调度线程,性能更好。并且具有更好的扩展性 (能够提供更多的子类)
- 在线成通信的过程中,synchronized只能使用notify()唤醒随机的、优先级更高的wait线程,而Lock可以使用Condition()精确的唤醒某个线程
- synchronized与Lock的优先使用顺序:Lock→同步代码块(已经进入了方法体,分配了相应的资源)→同步方法(在方法体外)
-
线程同步的优点与不足:同步的方式虽然解决了线程安全的问题;但是操作同步代码时,只能有一个线程参与其他线程等待,相当于是一个单线程的过程,效率低。
-
练习:银行由一个账户,有两个储户分别向同一个账户存3000元,没次存1000,存三次。每次存完打印账户余额。
//todo
4.线程安全的懒汉式单例模式
-
懒汉式单例模式先声明后使用,每次线程进入时,判断当前声明的对象是否为空,若为空则实例化,在多线程操作中,对于对象的非空判断可能造成线程不安全,当一个线程判为空时,进入if这个时候如果当前线程阻塞,第一个线程还未将对象实例化,则会导致其他线程有机会继续进入if语句,导致线程不安全。
-
将线程不安全的懒汉式单例模式更新为安全的懒汉式单例模式
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 19:20 * @Description: 更新懒汉式单例模式,使其成为线程安全的 */ public class LazyModeSingletonSafe { public static void main(String[] args) { LazySingleton test1 = LazySingleton.getInstance(); LazySingleton test2 = LazySingleton.getInstance(); System.out.println(test1 == test2); } } class LazySingleton{ //1. 将类的构造器私有化 private LazySingleton(){} //2. 创建静态的内部类对象 private static LazySingleton instance = null; //3. 提供公共的静态对象,返回类的对象 /* 同步代码块的方式实现线程同步 */ public static LazySingleton getInstance(){ //方式一:效率稍差,因为每次进入时都得一个一个线程进,即使已经创建过对象了, //这样导致每一个到这里的线程都要等待前一个线程成功返回实例 //改进思路可将return放到synchronized外 // synchronized(LazySingleton.class){ // if(instance == null){ // instance = new LazySingleton(); // } // return instance; // } //方式二:效率较方式一高 if(instance == null){ synchronized(LazySingleton.class){ instance = new LazySingleton(); } } return instance; } /* 同步方法的方式实现线程同步 */ // public static synchronized LazySingleton getInstance(){ // if(instance == null){ // instance = new LazySingleton(); // } // return instance; // } }
5.线程死锁的问题
-
死锁:
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,IDE不会出现提示,控制台也不会出现提示,只是所有的线程都处于阻塞状态,无法继续
-
解决方法:
- 专门的算法
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
-
死锁演示:
import static java.lang.Thread.sleep; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 19:54 * @Description: 线程死锁的演示 */ public class DeadLock { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); //匿名子类的方式,子类继承Thread父类,并实现run()方法 new Thread(){ @Override public void run() { synchronized (s1){ s1.append("a"); s2.append("1"); //通过睡眠使线程阻塞 try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); //匿名子类的方式,实现Runnable接口的方式 new Thread(new Runnable() { @Override public void run() { synchronized (s2){ s1.append("c"); s2.append("3"); try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //通过睡眠使线程阻塞 synchronized (s1){ s1.append("b"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
6.线程的通信方式
-
线程通信涉及的三个方法:wait()、notify()、notifyAll()
-
wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
-
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果由多个线程被wait,就会优先唤醒优先级更高的线程。
-
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
-
三个方法wait()、notify()、notifyAll()使用的注意点:
- 三个方法必须在同步代码块中,或者同步方法中使用(sychronized)
- 三个方法的调用者必须是同步代码块中的同步监视器或者是同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
- wait()、notify()、notifyAll()桑额方法是定义在java.lang.Object这个根父类中的三个方法
-
sleep()和wait()的区别?
- 相同点:一旦执行方法,都可以使得当前得线程进入阻塞状态
- 不同点:
- 两个方法得声明位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用得要求不同:sleep()可以在任何需要的场景下调用;wait必须在同步代码块中或者同步方法中使用
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放同步监视器,wait()会释放同步监视器
-
练习一:使用两个线程打印1-100的整数。线程一和线程二交替打印
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 21:16 * @Description: 线程通信的例子:使用两个线程打印1-100的整数,线程一、线程二交替打印 * */ class Number implements Runnable{ private int number = 1; @Override public void run() { while (true){ synchronized (this){ //线程进入所以后就直接可以唤醒另外一个线程b, //此时的线程a已经持有锁,线程b得等待线程a完成当前线程的所有操作才能执行 notify(); if(number <= 100){ System.out.println(Thread.currentThread().getName() + ":" + number); number++; //当执行到这里的线程做完输出之后,进入阻塞状态,然后等待被线程b的唤醒 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); Thread thread1 = new Thread(number); Thread thread2 = new Thread(number); thread1.setName("线程一"); thread2.setName("线程二"); thread1.start(); thread2.start(); } }
练习二:生产者/消费者问题:
-
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(20件),如果生产者试图生产更多的产品,电源会叫生产者停止一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品
-
这里可能出现的两个问题:
- 生产者比消费者快时,消费者会漏掉一些数据没有取到
-
消费者比生产者快时,消费者会取到相同的数据
/** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/15 21:35 * @Description: 线程通信的应用:金典例题:生产者/消费者问题 * 分析: * 是否有多线程问题? 是,生产者线程,消费者线程 * 是否有共享线程? 是,店员(或者产品) * 如何解决线程的安全问题? 同步机制,有三种方法 * 是否涉及线程的通信? 是 */ /** 店员 */ class Cleck{ private int productCount = 0; /** 生产产品 */ public synchronized void produceProduct() { if(productCount < 20){ //最开是产品数量为0,先自增,而后每次进入生产都是生产完了再输出当前已生产的产品数量 productCount++; System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品。"); //在生产完成之后就店员可以通知消费者消费产品 notify(); }else{ //生产的产品达到最大数量 //等待消费者消费掉第20个产品才开始生产 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } /** 消费产品 */ public synchronized void consumeProduct() { if(productCount > 0){ System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品。"); //在消费完产品之后,产品数量自减 productCount--; //在消费完产品之后,店员就可以通知生产者生产商品 notify(); }else{ //可消费的产品不足 //等待生产者生产第1个产品才开始消费 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** 生产者 继承于Thread类 */ class Producer extends Thread{ private Cleck cleck; /** 生产者的构造方法 */ public Producer(Cleck cleck){ this.cleck = cleck; } /** 重写父类中的run方法 */ @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始生产产品..."); while (true){ cleck.produceProduct(); } } } /** 消费者 继承于Thread类 */ class Consumer extends Thread{ private Cleck cleck; /** 消费者的构造方法 */ public Consumer(Cleck cleck){ this.cleck = cleck; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始消费产品..."); while (true){ cleck.consumeProduct(); } } } public class ProductTest { public static void main(String[] args) { Cleck cleck = new Cleck(); Producer p1 = new Producer(cleck); p1.setName("生产者1"); Consumer c1 = new Consumer(cleck); c1.setName("消费者1"); p1.start(); c1.start(); } }
-
7.创建多线程的方式三、四(JDK5.0后所支持)
-
创建多线程的方式三:实现Callable接口
-
与使用Runnable相比,Callable功能更强大些:
①相比run()方法,Callable可以有返回值
②Callable可以抛出异常
③支持泛型的返回值
④需要借助FutureTask类,比如获取返回值的结果
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/16 9:08 * @Description: 实现Callable接口(JDK5.0后支持) */ /** 1. 创建一个实现Callable的实现类 */ class NumThread implements Callable { /** * 2. 实现call()方法,将此线程需要执行的操作声明在call()方法中 * 重写父类Callable中的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 NewThread { 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 { //get()返回值即为FutureTask构造参数Callable实现类重写call()的返回值。 Object sum = futureTask.get(); System.out.println("1~100的偶数和为:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
-
-
创建多线程的方式四:使用线程池
-
现象:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
-
解决思路:提前创建好线程,放入线程池中,使用时直接获取,使用完放回线程池中。可以避免频繁创建和销毁、实现重复利用。
-
使用线程池的好处:
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于管理线程:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
-
-
JDK5.0以后提供了线程池相关的API:ExecutorService和Executors
- ExecutorService:正真的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- <T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭线程池
- Executor:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executor.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executor.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
- Executor.newSingleThreadExecutor():创建只有一个线程的线程池
- Executor.newScheduledThreadPool():创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/16 9:35 * @Description: 创建线程的方式四:使用线程池 */ class NumThread1 implements Runnable{ @Override public void run() { for (int i = 1; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class NumThread2 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; //2. 执行指定的线程操作,需要提供Runnable接口或Callable接口实现类的对象 //适合使用于Runnable //使用匿名子类的方式创建Runnable线程 service.execute(new NumThread1()); service.execute(new NumThread2()); //设置连接池的属性 //设置核心池的大小 service1.setCorePoolSize(10); //3. 关闭线程池的连接 service.shutdown(); //适合使用于Callable // service.submit(); } }
- ExecutorService:正真的线程池接口。常见子类ThreadPoolExecutor
-
8.创建线程的方式有几种?
-
有四种:
①子类继承Thread类的方式创建线程
②实现Runnable接口的方式创建线程
③JDK5.0后,实现Callable接口的方式创建线程
④JDK5.0后,使用ThreadPoolExectuor类或Exectuor类的方式创建线程池
9.实现线程同步的方式有几种?
-
有三种:
①使用同步代码块实现线程同步:synchronized(){}
②使用同步方法实现线程同步:权限修饰符 synchronized 返回值 方法名(){}
③使用java.util.concurrent.locks.Lock接口实现线程同步