线程基础与Synchronized的使用

一. 线程基础

1. 多线程概述

什么是进程,与线程的区别 : 在系统上运行的程序都是一个进程,每个进程中包含多个线程
什么是多线程,多线程的优点 : 在一个进程中,同一时刻多个不同执行路径并行执行,提高程序的执行效率
线程的分类 : 用户线程,守护线程,&&主线程,子线程用户线程是在主线程中创建的子线程,不会受主线程影响,守护线程会跟随主线程的销毁而销毁,用户线程调用setDaemon(true);方法后就变为了守护线程

2. 多线程的创建方式

  1. 继承Thread类
class CreateRunnable implements Runnable {

	@Override
	publicvoid run() {
		for (inti = 0; i< 10; i++) {
			System.out.println("i:" + i);
		}
	}

}
  1. 实现Runnbale接口
class CreateRunnable implements Runnable {

	@Override
	publicvoid run() {
		for (inti = 0; i< 10; i++) {
			System.out.println("i:" + i);
		}
	}
}

  1. 匿名内部类方式
	System.out.println("-----多线程创建开始-----");
		 Thread thread = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i< 10; i++) {
					System.out.println("i:" + i);
				}
			}
		});
		 thread.start();
		 System.out.println("-----多线程创建结束-----");

3. 线程相关方法

在这里插入图片描述
方法示例

public class ThreadDemo {
   public static void main(String[] args) {

     Thread t = Thread.currentThread();
     t.setName("Admin Thread");//设置线程名字
     // set thread priority to 1
     t.setPriority(1);//设置线程优先级
     
     // prints the current thread
     System.out.println("Thread = " + t);
    
     int count = Thread.activeCount();//获取当前线程组中存活的线程数量
     System.out.println("currently active threads = " + count);//结果为2(守护线程?)
     Thread th[] = new Thread[count];//创建一个线程数组,长度为获取获取到的存活的线程
     // returns the number of threads put into the array 
     Thread.enumerate(th);//将当前线程组及其子组中的每个活动线程,复制到指定线程组th中
    
     // prints active threads
     for (int i = 0; i < count; i++) {
        System.out.println(i + ":--- " + th[i]);
     }
   }
}

4. 线程的状态

在这里插入图片描述

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):
    Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):
    当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。而在Java中,阻塞态专指请求锁失败时进入的状态。由一个阻塞队列存放所有阻塞态的线程。处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
    PS:锁、IO、Socket等都资源。
  4. 等待(WAITING):
    当前线程中调用wait、join、park函数时,当前线程就会进入等待态。也有一个等待队列存放所有等待态的线程。线程处于等待态表示它需要等待其他线程的唤醒才能继续运行。进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
  5. 超时等待(TIMED_WAITING):
    当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后能够自动唤醒,不要其他线程唤醒;进入该状态后释放CPU执行权 和 占有的资源。
    与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
  6. 终止(TERMINATED):
    表示该线程已经执行完毕,当线程的run()方法完成时,或者主线程的main()方法完成时,在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
  7. 注意点:
    wait()方法会释放CPU执行权 和 占有的锁。
    sleep(long)方法仅释放CPU使用权,锁仍然占用;线程被放入超时等待队列,与yield相比,它会使线程较长时间得不到运行。
    yield()方法仅释放CPU执行权,锁仍然占用,线程会被放入就绪队列,会在短时间内再次执行。
    wait和notify必须配套使用,即必须使用同一把锁调用,并且wait和notify必须放在一个同步块中
    调用wait和notify的对象必须是他们所处同步块的锁对象。(不然报错IllegalMonitorStateException)

5. 线程的优先级 Join() 与 yield()

Join方法: 有两个线程,线程A与线程B, 当在A线程中,B线程调用join()方法,那么A线程会等待B线程执行完毕后执行
Thread.yield()方法: 的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。但是无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

//t1为子线程,t1.join()是在main方法,主线程中执行的
Thread t1 = new Thread(new Runnable() {
	@Override
	public void run() {
	   for (int i = 0; i < 10; i++) {
		try {
			Thread.sleep(10);
		} catch (Exception e) {

		}
					System.out.println(Thread.currentThread().getName() + "i:" + i);
				}
			}
		});
		t1.start();
		// 当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1
		t1.join();
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(10);
			} catch (Exception e) {

			}
			System.out.println("main" + "i:" + i);
		}

通过一个int priority来控制优先级 范围为1-10,其中10最高,默认值为5。通过线程对象调用setPriority(x);

public class PriorityThread {

    public static void main(String[] args) {
        //创建子线程对象
        MyThread02 myThread = new MyThread02();
        //通过线程对象创建两个线程,注意创建方式,将线程对象放入Thread构造器中
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);
        t1.start();
        // 注意设置了优先级,不代表每次都一定会被执行。 只是CPU调度会有限分配
        t1.setPriority(10);
        t2.start();
    }
}
class MyThread02 implements Runnable {

    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

二. Synchronized 的使用

1. Synchronized 实现多线程同步问题

Synchronizde 可以修饰方法,修饰代码块,修饰内部类,修饰对象等

  • 修饰非静态成员: 例如修饰this代码块,非静态方法等使用的是this锁,调用进入同步区域时就必须先获得对象锁。如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放,java的所有对象都含有一个互斥锁,这个锁由jvm自动获取和释放.synchronized方法正常返回或者抛异常而终止,jvm会自动释放对象锁。这里也体现了用synchronized来加锁的一个好处,即 :方法抛异常的时候,锁仍然可以由jvm来自动释放
  • **修饰静态成员:**例如修饰class,静态代码块,静态方法等不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。所以,一旦一个静态的方法被声明为synchronized。此类所有的实例对象在调用此方法,共用同一把锁,我们称之为类锁, 对象锁是用来控制实例方法之间的同步,而类锁是用来控制静态方法(或者静态变量互斥体)之间的同步的。类锁只是一个概念上的东西,并不是真实存在的,他只是用来帮助我们理解锁定实例方法和静态方法的区别的。java类可能会有很多对象,但是只有一个Class(字节码)对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,只不过是Class对象的锁而已。获取类的Class对象的方法有好几种,最简单的是[类名.class]的方式

2. Synchronized 死锁问题

假设调用method1()方法需要获取到锁A,调用method2()需要获取到锁B,现在的问题是方法1中调用方法2,方法2中又调用了方法1,都在等待对方释放锁

3. wait()和notify()

  • wait( ),notify( ),notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了**。当调用以上的方法的时候,一定要对竞争资源进行加锁,否则会报 IllegalMonitorStateException 异常**,在调用wait( )进行线程等待时,必须要取得这个锁对象的控制权(对象监视器),要放到synchronized(obj)代码中。
  • notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)
  • notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)
  • 举例: 假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1,thread2,thread3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,tread1,thread2,thread3只有一个有机会获得锁继续执行,例如tread1,其余的需要等待thread1释放obj锁之后才能继续执行。当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1,thread2,thread3虽被唤醒,但是仍无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1,thread2,thread3中的一个才有机会获得锁继续执行。
    如果一个线程调用了某个对象的wait方法,那么该线程进入到该对象的等待池中(并且已经将锁释放),
  • 如果未来的某一时刻,另外一个线程调用了相同对象的notify方法或者notifyAll方法, 那么该等待池中的线程就会被唤起,然后进入到对象的锁池里面去获得该对象的锁, 如果获得锁成功后,那么该线程就会沿着wait方法之后的路径继续执行。注意是沿着wait方法之后线程没有锁,对象才有锁,线程获取对象锁之后,会获取到cpu时钟片,执行同步代码块, 池锁里的线程若获得对象锁,那么会继续执行wait()之后的代码,要想其释放对象锁,只有以下几种情况:
    1. 执行完同步代码块。
    2. 线程终止。
    3. 对象的wait()方法被调用。
    4. 对象的notify()方法被调用。
发布了48 篇原创文章 · 获赞 0 · 访问量 575

猜你喜欢

转载自blog.csdn.net/qq_29799655/article/details/105513542