笔记整理之多线程

把之前的关于多线程的笔记整理了一下,如果有写的不对的地方,麻烦帮忙指出下,谢谢了。

目录

1不使用线程池的情况下,在一个方法中为什么不能重复调用同一个线程?

2线程对象调用run方法和调用start方法的区别:

3什么是高内聚,低耦合

4线程的生命周期

5创建线程的几种方式:

6多线程安全问题:

7多线程中解决同步问题的方式:

8Lock接口比synchronized的优势是什么

9死锁:

10wait和sleep的区别:

11多个线程按指定规则交替运行:


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();
		}
	}
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值