多线程
要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。
什么是进程呢?通过任务管理器我们就可以看到进程的存在。
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
多进程的意义:
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
什么是线程:
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率应该比较单线程程序抢到的概率要大。那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率。但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性。
大家注意两个词汇的区别:并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。
并行 : 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行
类 Thread 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
创建新执行线程的三种方法
方法一:
1.一种方法是将类声明为 Thread 的子类。
2.该子类应重写 Thread 类的 run 方法。
3.接下来可以分配并启动该子类的实例。
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
public void show(){
System.out.println("abc");
}
}
XXXX.run() 方法中的代码 将来由线程执行 一般会在其中放入一些耗时的代码
直接new对象调方法 并没有线程创建出来 这些代码还是运行在main中
正确开启线程方法–XXXX.start();
多次启动一个线程是非法的,特别是当线程结束执行后;想再开启可以再new一个新的对象
public class MyTest {
public static void main(String[] args) {
MyThread th= new MyThread();
th.start();
MyThread th2 = new MyThread();
th2.start();
}
}
this.getName(); 获取线程的名字
XXXX.setName(); 设置线程名字
多个线程同时 交替执行
Thread.currentThread(); 获取当前正在运行的线程对象
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
for (int i = 0; i <100; i++) {
// System.out.println(this.getName()+"-子线程执行"+i);
System.out.println(Thread.currentThread().getName()+ "-子线程执行" + i);
}
}
}
拿线程优先级 XXXX.getPriority();
设置线程优先级 XXXX.setPriority(); 最高MAX_PRIORITY=10;最低 MAX_PRIORITY=1;默认是5
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.setName("线程A");
th2.setName("线程B");
// 设置线程的优先级范围是 1---10 默认是5
th1.setPriority(Thread.MAX_PRIORITY);
th2.setPriority(Thread.MIN_PRIORITY);
int priority1 = th1.getPriority();
int priority2 = th2.getPriority();
System.out.println(priority1);// 10
System.out.println(priority2);// 1
th1.start();
th2.start();
}
}
让当前线程的休眠 单位是毫秒 Thread.sleep( XXXX ms );
public class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
System.out.println(this.getName()+"=="+i);
}
}
}
让线程从并发执行变成串行的方法 XXXX.join();
在线程启动start() 后再使用
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
// 效果就是刘关张三个线程先刘执行完 再让关执行完 最后让张执行完
th1.setName("刘备");
th2.setName("关羽");
th3.setName("张飞");
th1.start();
th1.join();
th2.start();
th2.join();
th3.start();
th3.join();
}
线程礼让: Thread.yield(); 用在线程run()内
暂停当前执行的线程 并执行其他线程 。让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。使用目的是让具有相同优先级的线程能适当轮换。
守护线程 :如果主线程(用户线程)结束后,子线程也立马结束,把子线程设为守护线程,XXXX.setDaemon(true); 在start()方法之前
在主线程关闭后无需手动关闭守护线程,会自动关闭,Java垃圾回收线程就是一个典型的守护线程 ,所有为线程服务而不涉及资源的线程都能设置为守护线程
线程的中断 强制 XXXX.stop(); 存在不安全性
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始执行了");
MyThread th = new MyThread();
th.setName("张飞");
th.start();
Thread.sleep(10);
th.stop(); //强制终止线程。
System.out.println("主线程执行完毕");
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(this.getName()+"=="+i);
}
}
}
清除阻塞 线程休眠就是阻塞状态的一种 XXXX.interrupt();
不能中断在运行中的线程,只能改变中断的状态而已
如下,如果某个线程正在使用sleep()暂停着,要取消他的等待状态,可以在正在执行的线程里调用interrupt()
public class MyTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始执行了");
MyThread th = new MyThread();
th.setName("张飞");
th.start();
th.interrupt();// 清除线程的阻塞
System.out.println("主线程执行完毕");
}
}
public class MyThread extends Thread{
@Override
public void run() {
try {
// 线程休眠,使线程处于了一种阻塞的状态。
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"=="+i);
}
}
}
多线程复制文件:复制两个文件
public class MyTest {
public static void main(String[] args) throws IOException {
System.out.println("开始复制文件");
new CopyMP4Thread().start();
new CopyTextThread().start();
System.out.println("文件复制完毕");
}
}
public class CopyTextThread extends Thread {
@Override
public void run() {
try {
Files.copy(Paths.get("MyTest.java"), Paths.get("C:C:\\Users\\Administrator\\Desktop\\MyTest.java"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class CopyMP4Thread extends Thread{
@Override
public void run() {
try {
Files.copy(Paths.get("Rec 2020-08-16 0002.mp4"), Paths.get("C:\\Users\\Administrator\\Desktop\\Rec 2020-08-16 0002.mp4"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
}
作业:多个线程,来共同复制一个文件
package org.westos.test;
import java.io.File;
public class one {
public static void main(String[] args) {
// 将一个文件均分给每一个线程一部分---RandomAccessStream中的.seek()
// 开启多线程复制方法:线程个数,文件长度,源文件,目标文件,以参数形式传入,方法里用一个for循环去开辟新线程
File file = new File("C:\\Users\\韩晨光\\Desktop\\摘要写作.docx");
startThread(10,file.length(),"C:\\Users\\韩晨光\\Desktop\\摘要写作.docx","C:\\Users\\韩晨光\\Desktop\\摘要写作2.docx");
}
/**
* 开启多线程复制
* @param threadnum
* 线程数
* @param fileLength
* 文件大小(用于确认每个线程下载多少东西)
* @param srcFilePath
* 源文件目录
* @param targerFilePath
* 目标文件目录
*/
private static void startThread(int threadnum,long fileLength,String srcFilePath,String targerFilePath) {
long modLength=fileLength%threadnum;
System.out.println(modLength);
long targetLength=fileLength/threadnum;
System.out.println(targetLength);
for (int i = 0; i < threadnum; i++) {
System.out.println((targetLength * i) + "-----" + (targetLength * (i + 1)));
new FileWriteThread((targetLength * i), (targetLength * (i + 1)), srcFilePath, targerFilePath).start();
}
if(modLength!=0){
System.out.println((targetLength * threadnum) + "-----" + (targetLength * threadnum + modLength));
new FileWriteThread((targetLength * threadnum), targetLength * threadnum + modLength + 1, srcFilePath, targerFilePath).start();
}
}
}
线程
package org.westos.test;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileWriteThread extends Thread {
private long begin;
private long end;
private RandomAccessFile srcFile;
private RandomAccessFile targetFile;
public FileWriteThread(long begin, long end, String srcFilePath, String targerFilePath) {
this.begin=begin;
this.end=end;
try {
this.srcFile=new RandomAccessFile(srcFilePath,"rw");
this.targetFile=new RandomAccessFile(targerFilePath,"rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void run() {
try {
srcFile.seek(begin);
targetFile.seek(begin);
int hasRead=0;
byte[] bytes = new byte[1];
while (begin<end&&-1!=(hasRead=srcFile.read(bytes))){
begin+=hasRead;
targetFile.write(bytes,0,hasRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
srcFile.close();
targetFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
创建线程的另外一种方式----implements Runnable
1.实现 Runnable接口的类;
2.该类实现run()方法;
3.分配该类的实例 new对象(创建任务);
4.newThread(将刚创建的对象作为参数传进去=把任务传递进来)。
常用方法:
获取名字 .currentThread().getName()
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。
扩展性更好
public class MyTest {
public static void main(String[] args) {
// 创建任务
MyRunnable myRunnable = new MyRunnable();
// 把任务传递进来
Thread th1 = new Thread(myRunnable,"刘亦菲");
Thread th2 = new Thread(myRunnable,"范冰冰");
th1.start();
th2.start();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
}
}
创建线程的方式三:
1.创建一个类实现Callable<>接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类 将FutureTask对象作为参数传进去
4.开启线程
public class MyTest {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(myCallable);
Thread thread = new Thread(task);
thread.start();
MyCallable myCallable1 = new MyCallable();
FutureTask<Integer> task1 = new FutureTask<>(myCallable1);
Thread thread1 = new Thread(task1);
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// call()将来是有线程来线程
System.out.println("线程过来执行了");
return null;
}
}
获取异步执行完的结果 .get(); 有返回值
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable1 = new MyCallable(100);
FutureTask<Integer> task1 = new FutureTask<>(myCallable1);
Thread thread = new Thread(task1);
thread.start();
// 获取异步执行完的结果
Integer integer1 = task1.get();
System.out.println(integer1);
MyCallable myCallable2= new MyCallable(50);
FutureTask<Integer> task2= new FutureTask<>(myCallable2);
Thread thread2 = new Thread(task2);
thread2.start();
Integer integer2 = task2.get();
System.out.println(integer2);
}
}
public class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <=num; i++) {
sum+=i;
}
return sum;
}
}
Runnable、Callable<>的区别:
Runnable重写run方法,没有返回值,无法获取异步执行完之后的结果,run方法无法抛出异常
Callable<>重写call方法,有返回值,可以获取异步执行完之后的结果。 call方法可以抛出异常。
线程安全问题
案例:买电影票 三个线程 卖100张票
当模拟网络延迟情况 会出现一些不合理的数据,也就是说出现了线程安全问题
1.出现重复票的问题—由于原子性(不可再分割性)所导致的 ++ --不是原子性操作,经过读改写操作
2.出现负票—由于线程的随机性导致的
出现线程安全方面问题的条件:
1.多线程环境
2.多个线程在操作共享数据
3.有没有多条语句在操作这个共享变量
我们可以使用同步代码块来解决线程安全方面的问题
synchronized 锁:就是java中任一个对象,多个线程要共享一把锁对象
synchronized (锁对象){
出现线程安全问题代码
}
synchronized 形容词 同步的 同步化的 (sei n krue nai zi de)
public class MyTest {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable);
Thread th2 = new Thread(cellRunnable);
Thread th3 = new Thread(cellRunnable);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class CellRunnable implements Runnable{
// 共享数据,被多个线程所共享。
int piao = 1000000;
static Object obj=new Object();
@Override
public void run() {
while (true) {
// 就是最后一张。piao=1;
synchronized (obj){
// 加锁
try {
// 模拟真实情况中,网络延迟的现象。
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
}
}
// 释放锁
}
}
}
给方法加锁synchronized—同步方法,同步方法的默认锁对象是this
public synchronized void maiPiao(){
// 加锁
try {
// 模拟真实情况中,网络延迟的现象。
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
}
}
静态同步方法 默认锁对象不是this 是当前类的字节码文件对象
//静态同步方法,用的锁对象是当前类的字节码文件对象
public static synchronized void maiPiao2() {
//就是最后一张。piao=1;
//加锁
try {
//模拟真实情况中,网络延迟的现象。
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
}
}
}
Lock锁的使用:
new ReentrantLock(); reentrant 可重入
.lock() 获取锁(加锁)
.unlock() 释放锁
建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中
public class MyTest {
public static void main(String[] args) {
CellRunnable cellRunnable = new CellRunnable();
Thread th1 = new Thread(cellRunnable);
Thread th2 = new Thread(cellRunnable);
Thread th3 = new Thread(cellRunnable);
th1.setName("窗口1");
th2.setName("窗口2");
th3.setName("窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class CellRunnable implements Runnable {
// 共享数据,被多个线程所共享。
int piao = 100;
static Object obj = new Object();
static Lock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
lock.lock();
try{
if (piao > 0) {
try {
// 模拟真实情况中,网络延迟的现象。
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 正在出售:" + (piao--) + " 张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
lock.unlock();
}
}
}
}
死锁问题:
多个线程由于争抢对方的锁 产生一种互相等待状态
如果出现了同步嵌套,就容易产生死锁问题
public class MyTest {
public static void main(String[] args) {
// 死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
/* 举例:
中国人和美国人一起吃饭
中国人使用的筷子
美国人使用的刀和叉
中国人获取到了美国人的刀
美国人获取到了中国人的一根筷子*/
MyThread th1 = new MyThread(true);
MyThread th2= new MyThread(false);
th1.start();
th2.start();
}
}
public interface ObjectUtils {
//创建 两把锁对象
Object objA=new Object();
Object objB = new Object();
}
public class MyThread extends Thread{
boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 两个或者两个以上的线程, 在抢占CPU的执行权的时候, 都处于等待状态
if(flag){
synchronized (ObjectUtils.objA){
System.out.println("true 线程进来了 持有objA锁");
synchronized (ObjectUtils.objB){
System.out.println("true 线程进来了 持有objB锁");
}
}// 释放锁
}else{
synchronized (ObjectUtils.objB) {
System.out.println("false 线程进来了 持有objB锁");
synchronized (ObjectUtils.objA) {
System.out.println("false 线程进来了 持有objA锁");
}
}// 释放锁
}
}
}
线程的等待唤醒机制
Object 类中
.wait(); 线程等待 一旦等待 就得释放锁 如果再次被唤醒 就从这里再次执行
.notify(); 通知线程 唤醒了正在等待的线程后 他们还要再次争抢
同步的窍门:如果加了同步还有线程安全问题,想两个前提:1,是不是两个或两个以上线程? 2.用的是不是同一把锁?
只同步了一个线程的代码是不行的,要将所有线程运行的代码都用同一把锁同步,这样一个执行同步代码,其他的判断同一把锁,才无法进入自己执行的代码。
即使在一个Runnable的操作代码中是一句, 要同步的也不是一句:是几个要同步的线程要操作的所有代码。
public class resource {
public String club;
public String number;
public boolean flag=false;
}
public class producter extends Thread {
private resource resource;
int i = 0;
public producter(resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
synchronized (resource) {
if (resource.flag) {
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i % 2 == 0) {
resource.club = "Milan";
resource.number = "马尔蒂尼";
} else {
resource.club = "Inter";
resource.number = "萨内蒂";
}
resource.flag = true;
resource.notify();
}
i++;
}
}
}
public class consumer extends Thread {
private resource resource;
public consumer(resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
synchronized (resource) {
if (!resource.flag) {
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(resource.club+"==="+resource.number);
resource.flag=false;
resource.notify();
}
}
}
}
//test
public class test {
public static void main(String[] args) {
resource resource = new resource();
producter producter = new producter(resource);
consumer consumer = new consumer(resource);
producter.start();
consumer.start();
}
}
//Milan===马尔蒂尼 Inter===萨内蒂 交替