把之前的关于多线程的笔记整理了一下,如果有写的不对的地方,麻烦帮忙指出下,谢谢了。
目录
1不使用线程池的情况下,在一个方法中为什么不能重复调用同一个线程?
1不使用线程池的情况下,在一个方法中为什么不能重复调用同一个线程?
public static void main(String[] args) {
TicketDemo t1=new TicketDemo();
t1.start();
t1.start();//不能再执行了
这个涉及到线程的生命周期,当run()结束了,线程就死亡了,变成垃圾了。
使用线程池在一个方法中可以重复调用同一个线程,因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中,并不会销毁一个线程。
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源,创建线程需要找系统帮忙。
线程池原理,程序一开始的时候,创建多个线程对象,存储到集合中,需要线程,从集合中获取线程出来,Thread t=ArrayList.remove(线程序号) 用完ArrayList.add(线程序号)
可以将线程池比喻成门禁卡,重新制卡和销毁卡比较麻烦。通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
2线程对象调用run方法和调用start方法的区别:
new Thread类及其子类,就相当于开了个线程。开启新线程就是cpu有个新的执行路径。当Thread子类.start()运行时,cpu就会执行子类中的run方法(只执行run方法注意)。
线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。
3什么是高内聚,低耦合
高内聚指的是自己的事情自己做,能做的事情不让外人做,低耦合就是尽量降低类与类之间的联系。
4线程的生命周期
5创建线程的几种方式:
使用Runnable接口实现线程程序可以做到线程之间数据共享,而使用Thread继承实现线程程序则不可以。
继承和实现接口的区别?
1 继承只能单继承,实现接口可以多继承。
2 继承资源不能共享,实现接口资源可以共享,这个共享的意思就是共享一个成员变量(new Thread(同一个接口实现类))
3 继承开启线程是new 继承类.start()即可,实现接口是new Thread(new 接口实现类).start()
6多线程安全问题:
衡量线程安不安全就看线程中是否具有共享一个成员变量或属性,有就不安全,没有就安全。
1当多个线程执行同一段代码,或者执行各自独立代码的时候,线程1某一段代码还没执行完,就被线程2抢夺了CPU资源,然后线程1处于阻塞状态。
2当多个线程并发访问同一个数据资源的时候,比如一个对象里面的成员变量,那这个变量的值将可能出现不可控的情况。
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题结合单例多例:
1线程安全才能用单例,没有数据域的成员变量(不具有状态的属性),线程不安全用具有数据域成员变量的单例,多个线程共用一个单例,会出现成员变量最终结果不可控。
2线程安全可以用单例,线程不安全用多例。
7多线程中解决同步问题的方式:
public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现类对象
Tickets t = new Tickets();
//创建3个Thread类对象,传递Runnable接口实现类
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
7.1同步代码块的方式:
同步代码块: 在代码块声明上加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象,但多个线程时,要使用同一个锁对象才能够保证线程安全,没有锁的线程不能执行只能等。
public class Tickets implements Runnable{
//定义出售的票源
private int ticket = 1000;
private Object obj = new Object();
@Override
public void run(){
while(true){
//线程共享数据,保证安全,加入同步代码块
//同步代码块,锁对象可以是任意对象,也可以用synchronized(this),this代表当前锁对象是本类(Tickets)对象.
//线程遇到同步(synchronized)代码块后,线程判断同步锁还有没有,没有同步锁就阻塞
// 同步锁有的话,获取锁,进入同步中,去执行了,执行完毕后,出去了同步代码块,线程再将锁对象还回去(就是释放锁)
synchronized(obj) {
if (ticket > 0) {
//对票数判断,大于0,可以出售,变量--操作
try {
//sleep方法在休眠的过程中,不会释放锁对象。
Thread.sleep(10);
} catch (Exception ex) {
}
System.out.println(Thread.currentThread().getName() + " 出售第 " + ticket--);
}
}
}
}
}
7.2同步方法的方式
同步方法:在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
public class Tickets implements Runnable{
private int ticket = 100;
@Override
public void run(){
while(true){
payTicket();
}
}
/* 采用同步方法形式,解决线程的安全问题,好处: 代码简洁
* 将线程共享数据,和同步,抽取到一个方法中
* 在方法的声明上,加入同步关键字
* 同步方法中的对象锁,是本类对象引用 this
* 静态方法,同步锁,是本类类名.class属性
* 静态同步方法: 在方法声明上加上static synchronized
* public static synchronized void method(){
* 可能会产生线程安全问题的代码
* }
*/
public synchronized void payTicket(){
if( ticket > 0){
try{
Thread.sleep(10);
}catch(Exception ex){}
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}
}
}
7.3lock锁方式
Lock接口,实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作,因为使用synchronized只有出了同步方法才会释放锁,假如程序运行过程中,
报错了,程序没有运行下去,没有出同步方法,那么锁就不能释放,所以Lock就应运而生了。Lock接口的用法灵活性都要比synchronized要方便要好。
public class Tickets implements Runnable{
//定义出售的票源
private int ticket = 100;
//在类的成员位置,创建Lock接口的实现类对象
private Lock lock = new ReentrantLock();
@Override
public void run(){
while(true){
//调用Lock接口方法lock获取锁
lock.lock();
//对票数判断,大于0,可以出售,变量--操作
if( ticket > 0){
try{
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
}catch(Exception ex){
}finally{
//释放锁,调用Lock接口方法unlock
lock.unlock();
}
}
}
}
}
8Lock接口比synchronized的优势是什么
1 lock能够显示地获取和释放锁,锁的运用更加灵活
2 lock可以方便地实现公平锁,synchronized只支持非公平锁,大部分情况下,非公平锁的效率是比较高的。
Lock lock = new ReentrantLock(false非公平锁,没有参数就是默认非公平锁);
Lock lock = new ReentrantLock(true公平锁);
公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来进行分配的,即先来先得先进先出顺序。
非公平锁:一种获取锁的抢占机制,是随机拿到锁的,和公平锁不一样的是先来的不一定先拿到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的。
9死锁:
同步锁的弊端:
当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这是容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种现象我们能避免就避免。
死锁就是两个线程都在等对方的线程释放锁。因为锁对象具有唯一性。
线程加上同步之后依然不安全的原因如下:
1 所有的共享数据都加上了吗?
2 多个线程是不是用的同一个锁对象,共用一个锁对象,才能锁住,加了同步锁的线程,只有拿到锁才能执行。
死锁产生示例:A在等B释放锁的同时B也在等A释放锁。
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock dead = new DeadLock();
Thread t0 = new Thread(dead);
Thread t1 = new Thread(dead);
t0.start();
t1.start();
}
}
public class DeadLock implements Runnable{
private int i = 0;
public void run(){
while(true){
if(i%2==0){
//先进入A同步,再进入B同步
synchronized(LockA.locka){
System.out.println("if...locka");
synchronized(LockB.lockb){
System.out.println("if...lockb");
}
}
}else{
//先进入B同步,再进入A同步
synchronized(LockB.lockb){
System.out.println("else...lockb");
synchronized(LockA.locka){
System.out.println("else...locka");
}
}
}
i++;
}
}
}
public class LockA {
private LockA(){}
public static final LockA locka = new LockA();
}
public class LockB {
private LockB(){}
public static final LockB lockb = new LockB();
}
10wait和sleep的区别:
wait方法来自于Object类,必须由锁对象调用,锁对象还必须存放在同步中。
sleep方法来自于Thread类,是Thread类的静态方法,可以类名点调用
Thread.sleep(time:毫秒):让当前线程休眠xxx毫秒,休眠之后,线程继续运行
锁对象.wait(time:毫秒):wait方法是传入毫秒值参数的,产生的效果和sleep方法类似
wait()方法:空参数,会让线程进入无限等待的状态,进入了无限等待的状态后,必须由notify()或者notifyAll()方法对其进行唤醒
wait()方法在等待的过程中,被唤醒了会释放锁,而sleep方法在休眠的过程中,不会释放锁对象。
11多个线程按指定规则交替运行:
创建三个线程,让3个线程按指定规则交替运行:
public class ThreadDemo {
public static void main(String[] args) {
Printer pt = new Printer();
new Thread() {
@Override
public void run(){
while(true){
try {
pt.print0();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run(){
while(true){
try {
pt.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run(){
while(true){
try {
pt.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer{
int flag=0;
public void print0() throws InterruptedException {
synchronized (Printer.class){
/*如果改成if判断的话,线程不会按照指定规则运行因为wait方法是在哪里等待就在哪里醒来,
醒来之后就继续向下执行代码,醒来之后直接执行下一句system输出代码,不会再进行判断了,所以改用
while进行重复判断
*/
while (flag!=0){
Printer.class.wait();
}
System.out.println(Thread.currentThread().getName()+"print0执行了,flag:"+flag);
flag=1;
//notify方法是随机唤醒单个线程,notifyAll是唤醒所有等待的线程
Printer.class.notifyAll();
}
}
public void print1() throws InterruptedException {
synchronized (Printer.class){
while (flag!=1){
Printer.class.wait();
}
System.out.println(Thread.currentThread().getName()+"print1执行了,flag:"+flag);
flag=2;
Printer.class.notifyAll();
}
}
public void print2() throws InterruptedException {
synchronized (Printer.class){
while (flag!=2){
Printer.class.wait();
}
System.out.println(Thread.currentThread().getName()+"print2执行了,flag:"+flag);
flag=0;
Printer.class.notifyAll();
}
}
}