一、多线程
1.1、多线程概述
进程
- 进程是程序的基本执行实体
线程
- 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
- 简单理解:应用软件中互相独立,可以同时运行的功能
什么是多线程?
- 有了多线程,我们就可以让程序同时做多件事情
多线程的作用?
- 提高效率
多线程的应用场景?
- 只要你想让多个事情同时运行就需要用到多线程
比如∶软件中的耗时操作、所有的聊天软件、所有的服务器
1.2、并发和并行
并发: 在同一时刻,有多个指令在单个CPU上交替执行
并行: 在同一时刻,有多个指令在多个CPU上同时执行
1.3、多线程的实现方式
- 继承Thread类的方式进行实现
- 自己定义一个类继承
Thread
- 重写run方法
- 创建子类的对象,并启动线程
- 使用
start
方法
- 使用
- 自己定义一个类继承
package thread;
public class Dome1 {
public static void main(String[] args) {
thread1 t1 = new thread1();
thread1 t2 = new thread1();
t1.setName("线程一");
t2.setName("线程二");
//线程一和线程二会同时执行
t1.start();
t2.start();
}
}
package thread;
public class thread1 extends Thread {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() +"你好啊");
}
}
}
- 实现Runnable接口的方式进行实现
- 在自己定义的类中接上Runnable接口
- 重写里面的run方法创建自己的类的对象
- 在自己创建的类中使用
Thread.currentThread()
方法可以获取到当前线程的对象
- 在自己创建的类中使用
- 创建一个Thread类的对象,并开启线程
- 测试类中将自己创建的类传入到Thread类中
package thread;
public class Dome2 {
public static void main(String[] args) {
//创建第一个线程
thread2 t1 = new thread2();
Thread tt1 = new Thread(t1);
tt1.setName("线程一");
tt1.start();
//创建第二个线程
thread2 t2 = new thread2();
Thread tt2 = new Thread(t2);
tt2.setName("线程二");
tt2.start();
}
}
package thread;
public class thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
/*
Thread t = Thread.currentThread();
System.out.println(t.getName()+"hello world");
*/
//第二种获取对象的方式
System.out.println(Thread.currentThread().getName()+"hello world");
}
}
}
- 利用
Callable接口
和Future接口
方式实现
特点: 可以获取到多线程运行的结果- 创建一个类MyCallable实现callable接口
- 重写call(是有返回值的,表示多线程运行的结果)
- 创建Mycallable的对象(表示多线程要执行的任务)
- 创建FutureTask的对象(作用管理多线程运行的结果)
- 创建Thread类的对象,并启动(表示线程)
package thread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Dome3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建thread3对象
thread3 t1 = new thread3();
//创建FutureTask对象(管理多线程的结果)
FutureTask<Integer> ft = new FutureTask<>(t1);
Thread tt1 = new Thread(ft);
tt1.start();
//获取多线程运行的结果
Integer integer = ft.get();
System.out.println(integer);
}
}
package thread;
import java.util.concurrent.Callable;
public class thread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num = 0;
for (int i = 0; i <= 100; i++) {
num = num+i;
}
return num;
}
}
1.4、多线程中常见的成员方法
方法名称 | 说明 |
---|---|
String getName() | 返口此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep( long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority( int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon( boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
getName()
细节- 如果我们没有给线程设置名字,线程也是有默认的名字的
- 格式: Thread-X(X序号,从0开始的)
- 如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
currentThread()
细节- 当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程
- 他的作用就是去调用main方法,并执行里面的代码
- 在以前,我们写的所有的代码,其实都是运行在main线程当中
sleep( long time)
细节- 哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
- 方法的参数:就表示睡眠的时间,单位毫秒
- 当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
package thread;
public class Dome4 {
public static void main(String[] args) throws InterruptedException {
//可以在类中重写构造方法来实现给多线程赋值
thread4 tr1 = new thread4("aaa");
thread4 tr2 = new thread4("bbb");
Thread tt1 = new Thread(tr1);
Thread tt2 = new Thread(tr2);
tt1.start();
tt2.start();
System.out.println(Thread.currentThread().getName());//main
System.out.println("asaaaaaaaaaaaaaaaaaaaaaa");
//可以时jvm机睡眠 n毫秒
Thread.sleep(5000);
System.out.println("bbbbbbbbbbbbbbbbbbbbbbbb");
Thread.sleep(5000);
System.out.println("dddddddddddddddddddddddd");
}
}
方法名称 | 说明 |
---|---|
setPriority( int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
- 线程的优先级默认是5(包括main线程)
- 改变了优先级只是先执行的概率变高了,并不是绝对先执行
package thread;
public class Dome5 {
public static void main(String[] args) {
thread2 t1 = new thread2();
thread2 t2 = new thread2();
Thread tt1 = new Thread(t1);
Thread tt2 = new Thread(t2);
tt1.setName("aaaa");
tt2.setName("bbbb");
//System.out.println(tt1.getPriority());//5
//System.out.println(tt2.getPriority());//5
//改变了优先级只是先执行的概率变高了,并不是绝对先执行
tt1.setPriority(1);
tt2.setPriority(10);
tt1.start();
tt2.start();
}
}
package thread;
public class thread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
/*
Thread t = Thread.currentThread();
System.out.println(t.getName()+"hello world");
*/
//第二种获取对象的方式
System.out.println(Thread.currentThread().getName()+"hello world");
}
}
}
方法名称 | 说明 |
---|---|
final void setDaemon( boolean on) | 设置为守护线程 |
- 当其他的非守护线程执行完毕之后,守护线程会陆续结束
- 通俗易懂:当女神线程结束了,那么备胎也没有存在的必要了
package thread;
public class Dome6 {
public static void main(String[] args) {
thread1 t1 = new thread1();
thread1_1 t2 = new thread1_1();
Thread t11 = new Thread(t1);
Thread t22 = new Thread(t2);
t22.setDaemon(true);//创建一个守护线程,当t11执行完毕时t22也会陆续结束
t11.start();
t22.start();
}
}
package thread;
public class thread1 extends Thread {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(getName() +"你好啊");
}
}
}
package thread;
public class thread1_1 extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() +"你不好吗");
}
}
}
方法名称 | 说明 |
---|---|
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
- 尽可能地让结果均匀,由于是让出当前CPU的执行权,多线程再次抢夺执行权时,同样可能再次被原来线程抢夺到
package thread;
public class Dome7 {
public static void main(String[] args) {
thread5 t1 = new thread5();
thread5 t2 = new thread5();
Thread tt1 = new Thread(t1);
Thread tt2 = new Thread(t2);
tt1.start();
tt2.start();
}
}
package thread;
public class thread5 extends Thread {
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(getName() +"你不好吗");
}
//表示让出当前CPU的执行权
Thread.yield();
}
}
方法名称 | 说明 |
---|---|
public static void join() | 插入线程/插队线程 |
package thread;
public class Dome8 {
public static void main(String[] args) throws InterruptedException {
thread1 t = new thread1();
Thread t1 = new Thread(t);
t1.start();
//表示把t这个线程,插入到当前线程之前。
//当前线程:main线程
t1.join();
/*
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
Thread-0你好啊
你好
你好
你好
你好
你好
* */
//当前执行在mian线程下
for (int i = 0; i < 5; i++) {
System.out.println("你好");
}
}
}
package thread;
public class thread1_1 extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() +"你不好吗");
}
}
}
1.5、线程的生命周期
sleep()
方法执行结束之后会编程就绪状态
1.6、线程安全的问题
把操作共享数据的代码锁起来
格式:
synchronized(锁){
操作共享数据的代码
}
特点1: 锁默认打开,有一个线程进去了,锁自动关闭
特点2: 里面的代码全部执行完毕,线程出来,锁自动打开
- 细节:
- synchronized锁的代码必须在循坏外面,否则会导致一个线程执行完所有的代码
- synchronized锁对象一定要是唯一的,一般使用当前类的字节码文件:
类.class
如果没有锁代码会出现的问题:
- 静态变量会发生重复
- 静态变量会超出方法里给限定的范围
- 问题原因:
- 线程执行时,会有随机性
package threadTest;
public class test1 {
public static void main(String[] args) {
test1_thead t1 = new test1_thead();
test1_thead t2 = new test1_thead();
test1_thead t3 = new test1_thead();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package threadTest;
public class test1_thead extends Thread{
static int ticket = 0;
static Object obj = new Object();
@Override
public void run() {
while (true){
synchronized(test1_thead.class){
if (ticket<100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在销售第"+ticket+"张票");
}else {
break;
}
}
}
}
}