1、我们先来创建一个线程类,继承自Thread类,然后重写一下run()方法,如下所示:
package com.xzb.demo.JUC.duoThread;
public class MyThread extends Thread{
private String threadname;
public MyThread(String threadname){
this.threadname = threadname;
}
@Override
public void run() {
System.out.println(this.threadname+"线程启动!");
}
}
2、我们在主方法中创建5个不同的线程,然后执行run(),查看效果发现每次都和代码顺序一致的启动, 理论来说线程的启动和代码顺序无关,是不定的,实际上我们只是重写的run()只是一个普通方法,在执行时,线程并没有真正启动,我们将run改成start方法看一下效果,会发现,和我们预料的一致。
package com.xzb.demo.JUC.duoThread;
public class maintest {
public static void main(String [] args){
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
MyThread t3 = new MyThread("C");
MyThread t4 = new MyThread("D");
MyThread t5 = new MyThread("E");
//run方法的执行
// t1.run();
// t2.run();
// t3.run();
// t4.run();
// t5.run();
//start()方法的执行
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
3、当线程执行start()方法时,各个线程会抢占CPU资源,谁先抢到谁先执行,那么这个分配资源是由谁来执行的呢?那我们来看一下start()方法的源码,会发现真正执行分配资源的是本地方法(在JVM中的本地方法区的那些C函数)我们来验证一下,打开start的源码我们可以看到,首先start用同步代码块来修饰,然后会看到strat0()方法,是用native来修饰的,也就是本地方法。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
4、我们可以看到Thread类实现了Runnable接口,下面我们来看一下Runnable接口的作用,Runnable官方文档中这样写,Runnable为所有的线程主体类对象提供了统一的毁掉规范,意思就是我们所有的线程主体类调用了start()方法时,底层都会去调用start0()方法(C语言的本地方法去进行cpu资源的调度),最后还要回调run()方法。【Runnable】接口就是定义了这样的一个执行流程规范,并规定所有实现此接口的方法都要覆写run()方法,这样,就相当于将具体实现类的功能逻辑统一安排到run()方法中去执行。
接下来我们进行一下实验,首先将刚才继承Thread的类改成实现Runnable接口的类
package com.xzb.demo.JUC.duoThread;
public class MyThread implements Runnable{
private String threadname;
public MyThread(String threadname){
this.threadname = threadname;
}
@Override
public void run() {
System.out.println(this.threadname+"线程启动!");
}
}
但是,这时的对象并不知道执行start()方法,要借助Thread类来执行
package com.xzb.demo.JUC.duoThread;
public class maintest {
public static void main(String [] args){
Runnable R1 = new MyThread("A");
Runnable R2 = new MyThread("B");
Runnable R3 = new MyThread("C");
Runnable R4 = new MyThread("D");
Runnable R5 = new MyThread("E");
new Thread(R1).start();
new Thread(R2).start();
new Thread(R3).start();
new Thread(R4).start();
new Thread(R5).start();
}
}
我们通过一个卖票的模型来验证
package com.xzb.demo.JUC.ticket;
public class ticket_t {
public static void main(String[] args){
//定义一个主体类对象
Runnable runnable = new Ticket();
//定义3个线程共享这个主体类对象
new Thread(runnable,"A").start();
new Thread(runnable,"B").start();
new Thread(runnable,"C").start();
}
}
class Ticket implements Runnable{
private int num=20;
//卖票
@Override
public void run() {
for(int i=0;i<=20;i++){
if(this.num>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + this.num-- + "张票");
}
}
}
}
5、下面我们来看一下---线程的优先级
首先要明确的是,线程的优先级并不代表线程会先执行,cpu执行的原理是不断的在不同的线程间进行上下文切换,以固定的时间片来执行多任务,优先级高的线程只是代表会较多的分配时间片来执行该线程。
操作线程优先级的函数:线程优先级【1-10】
- public final int getPriority()【取得线程的优先级】
- public final void setPriority(int newPriority)【设置线程的优先级】
6、 下面我们了解一下yield和join
join方法可以让主线程等待子线程执行完毕之后才继续执行,在此期间主线程一直是阻塞状态(挂起等待),也就是说当我们需要让主线程在子线程执行完毕之后再执行,此时就可以调用子线程的join方法。
下面是join方法的对比实验:
没有执行join方法的结果是:总票数是0,是因为主线程在子线程执行之前就执行完了,所以为0
package com.xzb.demo.JUC.ticket;
public class ticket_t {
public static void main(String[] args) throws InterruptedException {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Thread ra = new Thread(t1,"A线程");
Thread rb = new Thread(t2,"B线程");
ra.start();
rb.start();
// ra.join();
// rb.join();
System.out.println("总票数是"+(t1.ticket+t2.ticket));
}
}
class Ticket implements Runnable{
public int ticket=0;
//卖票
@Override
public void run() {
for(int i=1;i<=20;i++){
ticket++;
}
}
}
执行join方法的结果是:总票数是40,因为主线程在A,B两线程执行完才执行的。
package com.xzb.demo.JUC.ticket;
public class ticket_t {
public static void main(String[] args) throws InterruptedException {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Thread ra = new Thread(t1,"A线程");
Thread rb = new Thread(t2,"B线程");
ra.start();
rb.start();
ra.join();
rb.join();
System.out.println("总票数是"+(t1.ticket+t2.ticket));
}
}
class Ticket implements Runnable{
public int ticket=0;
//卖票
@Override
public void run() {
for(int i=1;i<=20;i++){
ticket++;
}
}
}
7、在多线程问题中,我们就需要用到锁或者同步代码块等手段来控制某一个对象在同一时刻只能由一个线程来获取和操作来保证数据的一致性,下面我们先来认识一下对象中的锁和监视器的关系
如上图所示,每个对象中都会有此对象的锁,并且会有一个和这个锁关联的监视器,监视器独立于对象存在,当该对象的某些方法加锁或者是被synchronized关键字修饰时,在执行时就会找到关联的监视器来操作该对象的的锁进行加锁操作,此时当其他线程访问时,同样访问监视器就会发现已经上锁,获取对象失败,当执行结束后,同样由监视器释放锁。
同步代码块:
同步方法:
8、锁池和等待池的概念
java中每个对象都有两个池,一个是锁池,一个是等待池
说明:锁池和等待池的概念如上图所示,当线程A执行同步代码块时获取该对象的锁,当在同步代码块中执行wait()方法时,会释放当前线程获取的对象锁,并将该线程放入等待池中,当线程B获执行该对象的notifyAll()时,会将该对象等待池中所有的线程放入到锁池中,重新准备争夺对象锁,而执行notify()方法时,会将指定的线程从等待池中放到锁池中,这两个方法也就是线程的唤醒操作
9、线程的生命周期
- 新建状态(new):new一个线程,此时和其他对象一样,没有任何动态特征
- 就绪状态(Runnable表示可运行):线程调用了start方法,处于就绪状态,JVM会为其创建方法栈和程序计数器,这是线程还没开始运行,只是可以运行了
- 运行状态(Running):线程开始执行run方法的线程体,该线程处于运行状态
- 阻塞状态:阻塞分为三种,等待阻塞、同步阻塞、其他阻塞
- 等待阻塞:运行的线程执行了wait方法(该方法只可以在同步代码块中执行,也就是说此时线程拿到了锁),wait方法会释放锁,JVM把该线程放入对象的等待池中。
- 同步阻塞:运行的线程获取对象的同步锁,若该锁呗其他线程占用,JVM把当前线程放入到锁池中。
- 其他阻塞:线程执行sleep或join方法,或者发出了IO请求时,JVM会把该线程置为阻塞状态,当结束之后,在置为就绪状态。
- 线程睡眠:Thread.sleep()
- 线程等待:Object类的wait()方法,导致线程阻塞,知道其他线程调用此对象的notify()或者notifyAll()唤醒方法,唤醒之后,重新变为就绪状态。
- 线程让步:Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给优先级大于等于的线程。
- 线程加入:join方法,等待其他线程终止,在当前线程调用另一个线程的join方法,则当前线程转入阻塞状态,知道另一个线程运行结束,当前线程由阻塞转为就绪。例子:主线程等待子线程执行完在执行后面的内容。
- 线程IO:线程的某些IO操作吗,因为等待相关的资源进入了阻塞状态,比如监听键盘输入而进入阻塞状态。
- 线程唤醒:Object中的notify(),唤醒此对象监视器上等待的单个线程,notifyAll方法,唤醒此对象监视器上等待的所有线程。监视器本身也有唤醒指定线程的方法,建立多个监视器即可。
10、 java中的设计模式
10.1 单例模式:单例模式是让某个类在程序的一次运行中或者一个运行的容器中只产生一个对象
单例模式的特征:
- 构造方法私有化
- 将本类对象保存在一个静态属性中
- 提供一个静态方法获取本类对象
单例模式的实现分为饿汉式和懒汉式两种:
/**
* 【单例模式】
* 不管调用多少次,都是调用的同一个对象,要想实现这个,只能将构造方法私有化、实例化本类
* 这个唯一的对象实例化的实现上分为饿汉式和懒汉式
* 饿汉式----程序一运行,对象就被创建
* 懒汉式----当调用静态方法时再判断对象是否已创建,如果没有,再创建对象。
*/
public class danli {
public static void main(String [] args) {
//懒汉式测验
System.out.println(Single.getsingle());
System.out.println(Single.getsingle());
System.out.println(Single.getsingle());
System.out.println(Single.getsingle());
//饥汉式测验
System.out.println(Single2.getsingle());
System.out.println(Single2.getsingle());
System.out.println(Single2.getsingle());
System.out.println(Single2.getsingle());
}
}
/**
* 懒汉式单例模式
*/
class Single{
//实例化本类方法
private static Single single;
//构造方法私有化
private Single() {}
//构建静态方法访问类对象
public static Single getsingle(){
if(single==null){
single = new Single();
}
return single;
}
}
/**
* 饥汉式单例模式
*/
class Single2{
//实例化本类方法
private static Single2 Single2 = new Single2();
//构造方法私有化
private Single2() {}
//构建静态方法访问类对象
public static Single2 getsingle(){
return Single2;
}
}