多线程
重点:设计到线程安全问题,是否有共享数据?是否多线程需要操作共享数据?
5.0 Thread类斜体样式
5.1 多线程的创建:
- 创建一个继承与Thread类的子类
- 重写Thread类的run()方法–>线程需要执行的操作声明写在run()中
- 创建Thread类的子类对象
- 通过此对象调用start()方法执行即可
5.2 线程常用方法:
测试Thread中常用的方法:
* 1.start():启动线程,调用当前线程的run()方法
* 2.run():需要重写Thread类中的此方法,将创建线程需要执行的操作声明写在此方法中
* 3.currentThread():静态方法,返回执行当前代码的线程
* 4.getName()、setName():获取、设置当前线程的线程名
* 5.yield():释放当前cpu的执行权,但不一定会被其他线程拿到执行权,有可能还是当前线程
* 6.join():在线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,当线程b执行完之后,线程a
* 结束阻塞状态,才继续执行
* 7.stop:强制结束当前线程
* 8.sleep(5000):让当前线程睡眠5秒(单位是毫秒)
9.isAlive():判断当前线程是否存活
5.3 线程优先级
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
如何获取和设置当前线程的优先级?
–> getPriority()、setPriority()获取和设置线程执行优先级,
说明:高优先级的线程要抢占低优先级线程的cpu执行权,从概率上讲,高优先级的线程可能会高概率的执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
5.4 Runnable接口
实现Runnable接口的五部:
* 1.创建一个类实现Runnable接口
* 2.实现类实现Runnable接口中的抽象方法run()
* 3.实例化实现类(创建实现类对象)
* 4.将实例化对象作为参数传递到Thread主类的构造方法中
* MeThread meThread = new MeThread();-->实现类
* Thread thread = new Thread(meThread);-->主类构造器中
* 5.通过主类的对象调用线程的start()方法;start方法的俩个作用:①启动当前线程 ②调用当前线程的run()方法-->调用了Runnable类型的target的run()方法
5.5 比较创建线程的俩种方式:Thread和Runnable
-
开发中:优先选择实现Runnable接口的方式
-
原因:1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况,就不用再数据上添加static来修饰**
5.6 线程的生命周期
5.7 线程同步
5.8 在java中,通过同步机制解决线程安全问题
1、同步方式
- 实现Runnable接口
synchronized (c) {大括号中将需要被同步的代码放进来}
说明:1.操作共享数据代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
3.同步监视器-->俗称:锁-->任何一个类的对象,都可以称当锁(要求:多个线程必须要共用同一把锁)
4.同步的方式:解决了线程的安全问题。---优点
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低 ---局限性
5.再实现Runnable接口创建多线程方式中,可以考虑使用this充当同步监视器(锁)
class BuyT implements Runnable{
private int ticket = 100;
//Object obj = new Object();
cat c = new cat();
@Override
public void run() {
while (true) {
synchronized (c) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class BuyTicket {
public static void main(String[] args) {
BuyT ticket = new BuyT();
Thread thread = new Thread(ticket);
Thread thread2 = new Thread(ticket);
Thread thread3 = new Thread(ticket);
//调用start方法,进入就绪状态
thread.start();
thread2.start();
thread3.start();
}
}
//创建一个类,实现实例化创建对象
class cat{}
当使用实现Runnable的方式时,synchronized()中的条件可以是this、当前类的对象。但前提是this对象必须唯一
2.继承方式实现多线程
package Thread;
/**
*说明:在继承Thread类创建多线程中,慎用this充当同步监视器(锁),考虑使用当前类充当同步监视器,可能存在多个对象
* @author Victor
* date
*/
class tickets1 extends Thread{
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (tickets1.class) {
if (ticket > 0) {
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class Buy {
public static void main(String[] args) {
tickets1 tickets = new tickets1();
tickets1 tickets2 = new tickets1();
tickets1 tickets3 = new tickets1();
tickets.setName("窗口一");
tickets2.setName("窗二");
tickets3.setName("口三");
tickets.start();
tickets2.start();
tickets3.start();
}
}
2、同步方法
2.1使用同步方法解决实现Runnable接口的线程安全问题
package Thread;
/**
* @author Victor
* date
*/
/**
* @author Victor
* date
* 例子:创建三个卖票窗口,总票数为100张,使用实现Runnable接口方式
* 存在线程安全问题
*
* synchronized ()中的参数可以是任意一个类的对象;
*/
class BuyTa implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
//调用同步方法即可
show();
}
}
//通过方法抽取需要被同步的的代码块
//当前的同步监视器为‘this’,也就是当前类对象
public synchronized void show(){//this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
public class BuyTickets {
public static void main(String[] args) {
BuyTa ticket = new BuyTa();
Thread thread = new Thread(ticket);
Thread thread2 = new Thread(ticket);
Thread thread3 = new Thread(ticket);
thread.start();
thread2.start();
thread3.start();
}
}
2.2 关于同步方法的总结:
2.1 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
2.2 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
package Thread;
/**
* @author Victor
* date
*/
class tickets2 extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
//当前的同步监视器为-->当前类'tickets2.class'<--
public static synchronized void show(){
if (ticket > 0) {
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
}
}
}
public class BuyTicketes{
public static void main(String[] args) {
tickets2 tickets = new tickets2();
tickets2 tickets2 = new tickets2();
tickets2 tickets3 = new tickets2();
tickets.setName("窗口一");
tickets2.setName("窗二");
tickets3.setName("口三");
tickets.start();
tickets2.start();
tickets3.start();
}
}
2.3 使用Lock锁方式解决线程安全问题
线程安全问题优先使用顺序:
Lock --> 同步代码块(已经进入了方法体,分配了相应资源) -->同步方法(在方法体之外)
3.懒汉式线程安全
class bank{
private bank(){}
private static bank instance = null;
//当前同步监视器为bank.class
//public static synchronized bank getInstance(){
public static bank getInstance(){
//效率稍差,因为线程都是为了拿到结果
// synchronized (bank.class) {
// if (instance == null){
// instance = new bank();
// }
// }
/*效率稍高,当多线程环境下,一个线程进入同步代码块之后,创建对象
后续的线程只需要判断是否为空即可
*/
if (instance == null) {
synchronized (bank.class) {
if (instance == null) {
instance = new bank();
}
}
}
return instance;
}
5.9 死锁
定义:
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成线程死锁
- 出现死锁后,不会出现异常,不会提示,只是所有的线程都处于阻塞状态,无法继续
解决办法:
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
6.0 线程通信
线程通信的例子:使用俩个线程打印1-100,俩线程交替打印
* 涉及到三个方法:
* notify():一旦执行该方法,当前线程就进入阻塞状态,并释放同步监视器
* notifyAll():一旦执行该方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就会唤醒优先级高的线程
* wait():一旦执行该方法,就会唤醒所有被wait的线程
*
* 说明:
* 1.wait()、notify()、notifyAll()三个方法必须在同步代码块或同步方法汇总
* 2.wait()、notify()、notifyAll()三个方法的调用者必须是同步代码快或同步方法中的同步监视器,否则会出现
* 异常IllegalMonitorStateException。出现改以后常也有可能是三个方法中的某个方法并没有包含在同步方法或同步代码快中,所以
* 会出现与同步代码块中不同的同步监视器
* 3.wait()、notify()、notifyAll()三个方法都是定义在java.lang。Object中,因为所有类的对象都可以成为同步代码快中的同步监视器
* 要想所有的类的对象都能成为同步监视器,那么必须在Object中定义
@Override
public void run() {
notify();
while (true) {
synchronized (this) {
//新线程进入方法后,通过notify()方法唤醒被wait()等待的线程
//this.notify();
// notifyAll();
if (num < 16) {
System.out.println(Thread.currentThread().getName() +"线程优先级:"+Thread.currentThread().getPriority()+ ":" + num);
num++;
try {
//当线程拿到数据之后在此等待,等待下一个线程唤醒,下一个线程将锁拿过来释放,新的线程才能进入方法
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
7.0 创建多线程的第三种方式
创建多线程方式三:实现Callable接口 ----JDK 5.0 新增
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程的方式强大?
* 1.call()可以有返回值
* 2.call()可以抛出异常,被外面的操作捕获,获取异常信息
* 3.Callbale是支持泛型
class ThreadNum implements Callable{//实现Callable接口
@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 ThreadCallable {
public static void main(String[] args) throws Exception{
//创建Callable接口实现类对象
ThreadNum threadNum = new ThreadNum();
//将实现类对象做为参数传递FutureTask构造器中,创建FutureTask的对象
FutureTask task = new FutureTask(threadNum);
//将FutureTask的对象作为参数传递到Thread构造器中,创建Thread对象,调用start()方法
new Thread(task).start();
//获取Callable接口中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()返回值
Object value = task.get();
System.out.println("得到的返回值为:"+value);
//
boolean done = task.isDone();
System.out.println(done);
}
}
8.0 创建多线程的第四种方式
线程创建方式四:使用线程池
/**
* @author Victor
* date
*
* 创建线程的方式四:使用线程池
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗速度(重复利用线程池中的线程,不需要每次都创建)
* 3.便于线程管理:
* 如:
* corePoolSize:核心池大小
* MaximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
*/
class NumberThreadPool implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
class NumberThreadPool1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i+=3) {
if (i % 2 != 0){
System.out.println("线程二"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(10);
System.out.println("实现类"+pool.getClass());
//通过实现Executors接口的父类,可以管理其中线程运行状态、线程数等
ThreadPoolExecutor th = (ThreadPoolExecutor) pool;
// int poolSize = th.getPoolSize();
// System.out.println("池大小"+poolSize);
//使用Runnable接口实现
NumberThreadPool numberThreadPool = new NumberThreadPool();
NumberThreadPool1 numberThreadPool1 = new NumberThreadPool1();
//将实现类对象传入线程池
pool.execute(numberThreadPool);//execute适用于Runnable
pool.execute(numberThreadPool1);
//关闭连接池
pool.shutdown();
}
}
核心代码:通过实现接口父类,就可以操作线程池中所有的方法
ExecutorService pool = Executors.newFixedThreadPool(10);
System.out.println("实现类"+pool.getClass());
//通过实现Executors接口的父类,可以管理其中线程运行状态、线程数等
ThreadPoolExecutor th = (ThreadPoolExecutor) pool;
面试题:
问:synchronized 与 Lock的异同?
相同:俩者都可以解决线程安全问题
不同:synchronized 机制在执行完相应的同步代码以后,自动释放同步监视器。Lock需要手动的启动同步锁(Lock()),同时也需要手动结束同步锁的实现(unLock())
问:sleep() 和 wait()的异同?
同:一旦方法执行,都可以使得当前线程进入阻塞状态
不同:1)俩给方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2)调用要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中
3)是否会释放同步监视器:如果俩个方法都使用在同步代码或同步方法中,sleep()不会释放同步代码快(即锁),wait()会释放同步代码块(即锁)