多线程
程序:为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡(生命周期)
1,线程的创建
1.1,方式一:继承于Thread类
1,创建一个继承于thread类的子类
2,重写thread类的run()方法 //针对方法体的,将此线程声明的操作声明在run()方法中
3,创建Thread类的子类的对象
4,通过此对象调用start()方法
例题:遍历100以内的所有偶数
package com.milostart.java;
/**
* @author milostart
* @create 2020-09-08-10:20
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();//①启动当前线程 ②调用当前线程的run()方法
//如下操作仍然实在main线程中执行的
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i+"******main()********");
}
}
}
class MyThread extends Thread {
@Override
publicvoid run(){
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
1.1.1,创建过程中的两个问题
①问题一:我们不能通过直接调用run()方法的方式来启动新线程,必须调用start()
②问题二:再启动一个线程,遍历一百以内的偶数不可以还让已经start()的线程去执行,会报错
即:我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
1.1.2,线程中的常见方法
①start():启动当前线程:调用当前线程的run()
②run():通常需要重写thread类中的此方法,将创建的线程要执行的操作声明在此方法中
③currentThread():静态方法,返回执行当前代码的线程
④getName():获取当前线程的名字
⑤setName():设置当前线程的名字
注:给线程命名:
1,构造器命名:HelloThread h1 = new HelloThread(“Thread:1”)
2,给主线程命名:Thread.currentThread.setName(“主线程”);
⑥yield():释放当前CPU的执行权
注:this相当于当前类的对象(h1),相当于当前线程:(Thread.currentThread)
⑦join():在线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a结束阻塞状态
⑧stop():已过时,强制线程声明结束
⑨sleep(long milltime):,会直接抛异常,让当前线程睡眠指定milltime毫秒,在指定的milltime毫秒时间内当前线程是阻塞状态
如:sleep(1000);//毫秒为单位,1000为1秒
//不能用throws,因为run方法是重写的Thread当中的,而Thread中run()没有抛异常,所以不能用
//抛异常第一条规则就是不是抛的异常比父类中的大
try{
sleep(1000);
}catch(InterruptedException){
e.printStackTrace();
}
⑩isAlive():判断当前线程是否存在
如:h1.isAlive();//判断主线程是否存活
1.1.3,线程的调度
(设置线程的优先级)
说明:高优先级的线程要抢占低优先级cpu的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
1.2,方式二:实现Runnable接口
1,创建了一个实现Runnable接口的类
2,实现类去实现Runnable中的抽象方法:run()
3,创建实现类的对象
4,将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5,通过Thread类的对象调用start():①启动线程②调用当前线程run()方法–>调用了Runnable类型中target的run()
package com.milostart.java;
/**
* 创建多线程方式二
* 创建实现了Runnable接口的类
*
* @author milostart
* @create 2020-09-08-21:14
*/
public class ThreadTest1 {
public static void main(String[] args) {
//创建实现类的对象
MThread mThread = new MThread();
// 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.start();
}
}
class MThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
1.3,两种创建方式的比较
开发中:优先选择:实现Runnable接口的方式
原因:1,实现的方式没有类的单继承性的局限性
2,实现的方式更适合来处理多个线程有共享数据的情况
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中
2,线程的生命周期
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为的挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性的终止或出现异常导致结束
说明:
1,声明周期关注两个概念:状态,相应的方法
2,关注:状态a–>状态b:哪些方法执行了(回调方法)
某个方法主动调用:状态a–>状态b
3,阻塞:临时状态,不可以作为最终状态
死亡:最终状态
3,线程的同步
问题的提出:
1,多个线程执行的不确定性引起执行结果的不稳定
2,多个线程对账本的共享,会造成操作的不完整性,会破坏数据
3.1,方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码,操作共享数据的代码
}
说明:①操作共享数据的代码,即为需要被同步的代码
②共享数据:多个线程共同操作的变量,比如:ticket就是共享数据
③同步监视器:俗称锁,任何一个类的对象都可以充当锁 要求:多个线程必须要共用一把锁
注意:1,在实现Runnable接口创建多线程的方式中,可以考虑用this来充当同步监视器
2,在继承Thread类创建多线程的方式中,慎用this充当同步监视器
3.1.1通过同步代码块来处理实现Rubnable线程安全问题
package com.milostart.java;
/**
* @author milostart
* @create 2020-09-09-13:52
*/
public class WindowTest1 {
public static void main(String[] args) {
Window1 w1 = new Window1();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable {
private int ticket = 100;
//Object obj = new Object();
@Override
public void run() {
//this指代当前类的对象,即window1的对象,可以用来替代obj
synchronized (this) {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
3.1.2,同步代码块处理继承Thread类的线程安全问题:
package com.milostart.java;
/**
* 三个窗口卖票,总票数一百张
* 存在线程安全问题,当某个线程操作车票的过程中,尚未参与进来,其他线程参与进来
* 当一个线程a在操作ticket,其他操作不能参与进来,直到a操作完成
*
* @author milostart
* @create 2020-09-08-20:55
*/
public class WindowTest {
public static void main(String[] args) {
//由于建立了三个对象,所有共有三个ticket,三百张票,需要用静态变量来声明
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread {
//三个线程共用一个静态变量
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
//此处类也是对象
//Class clazz = Window2.class
synchronized (Window.class){
//synchronized (obj) {
while (true) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(getName() + "票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
3.2,方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
同步的方式:解决了线程的安全问题
操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程----局限性
注意:在实现Runnable接口创建多线程的方式中,可以考虑用this来充当同步监视器
3.2.1,通过同步方法来处理实现Rubnable线程安全问题:
package com.milostart.java;
/**
* @author milostart
* @create 2020-09-10-12:16
*/
public class WindowTest2<run> {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
class Window2 implements Runnable {
private int ticket = 100;
//Object obj = new Object();
//同步方法
//此时同步监视器为:this
private synchronized void show() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
//break用在循环当中
}
}
@Override
public void run() {
while (true) {
show();
}
}
}
3.2.2同步代码块处理继承Thread类的线程安全问题:
package com.milostart.java;
/**
* @author milostart
* @create 2020-09-10-12:30
*/
public class WindowTest3 {
public static void main(String[] args) {
//由于建立了三个对象,所有共有三个ticket,三百张票,需要用静态变量来声明
Window3 t1 = new Window3();
Window3 t2 = new Window3();
Window3 t3 = new Window3();
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
class Window3 extends Thread {
//三个线程共用一个静态变量
private static int ticket = 100;
private static Object obj = new Object();
//此时同步监视器为window3.class
private static synchronized void show() {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "票号为:" + ticket);
ticket--;
}
}
@Override
public void run() {
//synchronized (obj) {
while (true) {
show();
}
}
}
关于同步方法的总结:
①同步方法仍然涉及同步监视器,只是不需要我们显示声明
②非静态的方法,同步监视器是this
静态的方法,同步监视器是当前类本身
4,线程的通信
涉及三个方法:
①wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
②notify()一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个线程被wait(0,则就唤醒优先级最高的那个
③notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
注意点:
1,这三种方法必须使用在同步代码块或同步方法中
2,这三种方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会报错
3,这三种方法是被定义在java.lang.Object类中的
public class Communication {
private int number = 100;
public void run() {
while (true) {
synchronized (this) {
notify();
// synchronized (obj) {
// obj.notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()的线程进入阻塞状态
// obj.wait();
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
4.1,sleep()和wait()方式的异同:
相同点:都可以使得当前线程进入阻塞状态
不同点:
1,两个方法声明的位置不同:Thread类中声明sleep(),Object()类中声明wait()
2,调用要求不同:sleep()可以在任何需求的场景下使用,wait()必须使用在同步方法或同步代码块中
3,关于是否释放同步监视器的问题:
如果两个方法都使用在同步方法或同步代码块中,sleep(),wait()会释放同步监视器(即锁)
5,JDK5.0新增线程创建的方式
5.1,方式一:实现Callable接口
1,创建一个实现Callable的实现类
2,实现call方法,将此线程需要执行的操作声明在call()中
3,创建Callable接口实现类的对象
4,将此Callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask
5,将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用
6,获取Callable中call方法的返回值
如何理解实现Callable接口方式创建多线程的方式比实现Runnable接口的创建多线程的方式强大:
1,call()可以有返回值
2,call()可以抛出异常,被外面的操作捕获,获取异常的信息
3,Callable是支持泛型的
package com.milostart.java1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author milostart
* @create 2020-09-11-7:30
*/
//创建一个实现Callable的实现类
class NumverThhread implements Callable {
//2,实现call方法,将此线程需要执行的操作声明在call()中
@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 ThreadNew {
public static void main(String[] args) {
//3,创建Callable接口实现类的对象
NumverThhread numverThhread = new NumverThhread();
//4,将此Callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask
FutureTask futureTask = new FutureTask(numverThhread);
//5,将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用
new Thread(futureTask ).start();
try {
//6,获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
Object sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
5.2,方式二:使用线程池
步骤:
1,提供指定线程数量的线程池
2,执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
3,关闭连接池
package com.milostart.java1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author milostart
* @create 2020-09-11-8:14
*/
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1,提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//适合使用Runnable
//2,执行指定的线程操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());
service.execute(new NumberThread1());
//适合使用与Callable
// service.submit(Callable callable);
//3,关闭连接池
service.shutdown();
}
}