Java多线程一
基本概念
-
线程:线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
-
进程:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用
程序运行的载体。
进程与线程的区别:
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 线程上下文切换比进程上下文切换要快得多;
线程创建
方法一:继承Thread类,重写run方法
//线程创建方法一:继承Thread类,重写run方法,调用start方法
public class TestThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我是run线程"+i);
}
}
//线程开启不一定立刻执行,由CPU调度
public static void main(String[] args) {
//调用start方法才是线程并发执行,调用run方法就是线程顺序执行了
new TestThread1().start();
for (int i = 0; i < 500; i++) {
System.out.println("我是main线程"+i);
}
}
}
部分结果
我是main线程0
我是main线程1
我是run线程0
我是run线程1
我是run线程2
我是run线程3
我是run线程4
我是run线程5
我是run线程6
我是run线程7
我是run线程8
我是run线程9
我是run线程10
我是main线程2
实例参考
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread2 extends Thread{
private String url;
private String name;
public TestThread2(String url,String name){
this.url=url;
this.name=name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.download(url,name);
System.out.println("下载完成"+name);
}
public static void main(String[] args) {
TestThread2 testThread01 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fa%2F58e5a24fa7b46.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628562283&t=356c6b2898f83c8ca77e3545b861945d", "1.jpg");
TestThread2 testThread02 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fd%2F592789e0cdbc4.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628562283&t=4475b6c2e6bc8a72b1ee6801a7d86259", "2.jpg");
TestThread2 testThread03 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F8%2F573eb35e89eda.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628562283&t=b8e6e83086b5899f203b773e9762648a", "3.jpg");
testThread01.start();
testThread02.start();
testThread03.start();
}
}
class WebDownloader{
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("download方法异常");
}
}
}
结果
下载完成1.jpg
下载完成2.jpg
下载完成3.jpg
方法二:继承Runnable接口,重写run方法
//创建线程方法二:继承接口Runnable,重写run方法,把类的实例丢入Thread参数
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我是run线程"+i);
}
}
public static void main(String[] args) {
TestThread3 testThread3 = new TestThread3();
new Thread(testThread3).start();
for (int i = 0; i < 500; i++) {
System.out.println("我是main线程"+i);
}
}
}
部分结果
我是main线程0
我是main线程1
我是main线程2
我是run线程0
我是run线程1
我是run线程2
我是run线程3
我是run线程4
我是run线程5
我是run线程6
我是run线程7
我是run线程8
我是main线程3
我是run线程9
我是main线程4
方法三:继承Callable接口
//线程实现方法三:继承Callable方法
//Callable接口可以定义返回值
//重写call方法,需要抛异常
//创建目标对象
//创建执行服务ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行Future<Boolean> r1 = ser.submit(t1);
//获取结果Boolean result1 = r1.get();
//关闭服务ser.shutdownNow();
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url,String name){
this.url=url;
this.name=name;
}
@Override
public Boolean call() throws Exception {
WebDownload webDownload = new WebDownload();
webDownload.download(url,name);
System.out.println("下载完成"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fa%2F58e5a24fa7b46.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628562283&t=356c6b2898f83c8ca77e3545b861945d", "1.jpg");
TestCallable t2 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2Fd%2F592789e0cdbc4.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628562283&t=4475b6c2e6bc8a72b1ee6801a7d86259", "2.jpg");
TestCallable t3 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F8%2F573eb35e89eda.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628562283&t=b8e6e83086b5899f203b773e9762648a", "3.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果
Boolean result1 = r1.get();
Boolean result2 = r2.get();
Boolean result3 = r3.get();
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
//关闭服务
ser.shutdownNow();
}
}
class WebDownload{
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("download方法异常");
}
}
}
结果
下载完成1.jpg
下载完成2.jpg
下载完成3.jpg
true
true
true
静态代理模式
在Java中线程的设计就使用了静态代理设计模式,其中自定义线程类实现Runable接口,Thread类也实现了Runalbe接口,在创建子线程的时候,传入了自定义线程类的引用,再通过调用start()方法,调用自定义线程对象的run()方法。实现了线程的并发执行。
//静态代理模式
//代理对象----代理真实对象处理相关事务
//真实对象----真实对象和代理对象都继承了同一个接口,不过代理对象还可以定义一些真实对象无法进行的操作,真实对象只需要专注于自己的业务实现(接口方法实现)
//Runnable---继承的接口,Thread-----代理对象,继承Runnable的类------真实对象
//真实对象
public class Marry implements M{
@Override
public void HappyMarry() {
System.out.println("开心marry");
}
public static void main(String[] args) {
MarryCompany marryCompany = new MarryCompany(new Marry());
marryCompany.HappyMarry();
}
}
//接口
interface M{
public void HappyMarry();
}
//静态代理
class MarryCompany implements M{
private M marry;
public MarryCompany(M marry) {
this.marry = marry;
}
@Override
public void HappyMarry() {
//准备工作。。。
marry.HappyMarry();
//结束工作。。。
}
}
线程并发问题
//多个线程操作同一个资源
//发现操作异常,并发访问需要控制
public class TestThread4 implements Runnable{
int ticketNum = 10;
@Override
public void run() {
while (true){
if (ticketNum<=0){
System.out.println(Thread.currentThread().getName()+"没了");
break;
}
System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNum--+"张票");
}
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"小明").start();
new Thread(testThread4,"小红").start();
new Thread(testThread4,"小华").start();
}
}
结果:
小明买到了第10张票
小红买到了第9张票
小红买到了第7张票
小红买到了第6张票
小华买到了第10张票
小红买到了第5张票
小红买到了第3张票
小红买到了第2张票
小明买到了第8张票
小红买到了第1张票
小红没了
小华买到了第4张票
小明没了
小华没了
分析结果可以看出,小明和小华同时买到了第10张票,多个线程操作一个共享资源的时候可能会出现同时访问发生脏读,由此可以看出必须对并发的线程进行一定执行顺序上的约束以保障结果的可靠性与正确性。
死锁问题
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
产生死锁的四个必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不可剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
循环等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
public class DeadLock {
public static void main(String[] args) {
Mackup g1 = new Mackup(0, "可可爱");
Mackup g2 = new Mackup(1,"高冷冷");
g1.start();
g2.start();
}
}
class LipStick{
}
class Mirror{
}
class Mackup extends Thread{
private static LipStick lipStick=new LipStick();
private static Mirror mirror=new Mirror();
int choice;
String name;
public Mackup(int choice,String name){
this.name = name;
this.choice = choice;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipStick){
System.out.println(name+"拿到了口红");
Thread.sleep(100);
synchronized (mirror){
System.out.println(name+"拿到了镜子");
}
}
}else {
synchronized (mirror){
System.out.println(name+"拿到了镜子");
Thread.sleep(100);
synchronized (lipStick){
System.out.println(name+"拿到了口红");
}
}
}
}
}
死锁结果
可可爱拿到了口红
高冷冷拿到了镜子
解决方法(这里使用的是破坏四个必要条件中的请求和保持条件)
public class DeadLock {
public static void main(String[] args) {
Mackup g1 = new Mackup(0, "可可爱");
Mackup g2 = new Mackup(1,"高冷冷");
g1.start();
g2.start();
}
}
class LipStick{
}
class Mirror{
}
class Mackup extends Thread{
private static LipStick lipStick=new LipStick();
private static Mirror mirror=new Mirror();
int choice;
String name;
public Mackup(int choice,String name){
this.name = name;
this.choice = choice;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipStick){
System.out.println(name+"拿到了口红");
Thread.sleep(100);
}
//执行到这里该线程已经释放了lipStick的锁
synchronized (mirror){
System.out.println(name+"拿到了镜子");
}
}else {
synchronized (mirror){
System.out.println(name+"拿到了镜子");
Thread.sleep(100);
}
//执行到这里该线程已经释放了mirror的锁
synchronized (lipStick){
System.out.println(name+"拿到了口红");
}
}
}
}
线程的常见方法使用
currentThread()
返回对当前正在执行的线程对象的引用。
public static native Thread currentThread();
yield()
放弃当前线程的执行,由调度程序重新调度选择下一个要执行的线程,调度程序可以自由忽略。
public static native void yield();
sleep()
导致当前执行的线程睡眠(暂时停止执行),时间为指定的毫秒,取决于系统定时器和调度器的精度和准确性。该线程不会失去对任何监视器的所有权。
参数:
millis - 以毫秒为单位的睡眠时间长度。
抛出异常:
IllegalArgumentException - 如果millis的值是负的。
InterruptedException - 如果任何线程打断了当前线程。当这个异常被抛出时,当前线程的中断状态被清除。
public static native void sleep(long millis) throws InterruptedException;
sleep()
导致当前执行的线程睡眠(暂时停止执行),时间为指定的毫秒数加上指定的纳秒数,取决于系统定时器和调度器的精度和准确性。该线程不会失去对任何监视器的所有权。
参数:
millis - 以毫秒为单位的睡眠时间长度
nanos - 0-999999额外的纳秒睡眠时间
抛出异常:
IllegalArgumentException - 如果millis的值为负数,或者nanos的值不在0-999999的范围内。
InterruptedException - 如果任何线程打断了当前线程。当这个异常被抛出时,当前线程的中断状态被清除。
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;//从这个可以看出实际执行还是把纳秒舍人为毫秒来执行的
}
sleep(millis);
}
clone()
可能抛出CloneNotSupportedException,因为一个线程不能被有意义地克隆。构建一个新的线程来代替。
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
start()
一个线程的开始,执行之后java虚拟机调用该线程的run方法
结果是两个线程同时运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)。
启动一个线程超过一次是不合法的。特别是,一旦一个线程完成了执行,就不能再重新启动它。
抛出异常:
IllegalThreadStateException - 如果线程已经被启动。
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();
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
run()
如果线程使用Runnable构造的,则它的调用run方法,否则不执行任何操作并返回。
public void run() {
if (target != null) {
target.run();
}
}
interrupt()
中断调用方法的进程。
除非当前线程正在中断自身(这是始终允许的),否则将调用此线程的checkAccess方法,这可能会导致抛出SecurityException。
如果此线程在调用对象类的wait()、wait(long)或wait(long,int)方法时被阻塞,或者在调用此类的join()、join(long)、
join(long,int)、sleep(long)或sleep(long,int)方法时被阻塞,则其中断状态将被清除,并且它将接收InterruptedException。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
interrupted()
测试当前线程是否已被中断,此方法清除线程的中断状态 。 换句话说,如果连续两次调用此方法,则第二次调用将返回false(除非当前线程在第一次调用已清除其中断状态之后且在第二次调用检查之前再次中断)。
如果中断返回true,否则返回false
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
setPriority()
更改该线程的优先级,会依次检查权限、优先级是否越界等,都满足后设置优先级。
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
getPriority()
返回线程的优先级。
public final int getPriority() {
return priority;
}
进程同步
在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系
使用关键字synchronized修饰类
同步锁使用场景:多个线程对同一个对象中的实例变量进行并发访问。
public class kt {
public static void main(String[] args) {
System.out.println("使用关键字synchronized");
SyncThread syncThread = new SyncThread();//锁定的对象
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized (this)//锁的是对象,对象!!!
{
for (int i = 0; i < 5; i++) {
try {
System.out.println("线程名:"+Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
结果
使用关键字synchronized
线程名:SyncThread1:0
线程名:SyncThread1:1
线程名:SyncThread1:2
线程名:SyncThread1:3
线程名:SyncThread1:4
线程名:SyncThread2:5
线程名:SyncThread2:6
线程名:SyncThread2:7
线程名:SyncThread2:8
线程名:SyncThread2:9
使用关键字synchronized修饰方法
synchronized 关键字在方法声明中使用时,是起到锁的这样一个作用 : 每次有且只有一个线程执行该方法的方法体。
对象锁和静态锁:
使用对象锁分为:synchronized(this)锁,synchronized(非this对象)锁。synchronized(this)锁与synchronized关键字在方法声明是一样的作用,优点都是解决多线程同步问题。synchronized(非this对象),对比与synchronized(this)的优点:提高多个方法同步的效率问题。
静态锁:应用在static静态方法上,锁为当前*.java文件的Class类。
public class kt2 {
public static void main(String[] args) {
System.out.println("使用关键字静态synchronized");
SyncThread2 syncThread1 = new SyncThread2();
SyncThread2 syncThread2 = new SyncThread2();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
class SyncThread2 implements Runnable {
private static int count;
public SyncThread2() {
count = 0;
}
public synchronized static void method() {//修饰静态方法时每一个类的对象都会使用同一把锁
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void run() {
method();
}
}
结果
使用关键字静态synchronized
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
使用锁Lock手动设定边界,更加灵活
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock1 = new TestLock2();
TestLock2 testLock2 = new TestLock2();
new Thread(testLock1).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
private static final ReentrantLock lock = new ReentrantLock();//多线程常用的可重入锁
static int ticketNum = 10;
@Override
public void run() {
while (true){
try{
lock.lock();
System.out.println(ticketNum--);
Thread.sleep(1000);
if (ticketNum<=0){
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
结果
10
9
8
7
6
5
4
3
2
1
0
进程通信
进程通信是指在进程间传输数据(交换信息)。 进程通信根据交换信息量的多少和效率的高低,分为低级通信(只能传递状态和整数值)和高级通信(提高信号通信的效率,传递大量数据,减轻程序编制的复杂度)。其中高级进程通信分为三种方式:共享内存模式、消息传递模式、共享文件模式。
生产者消费者模型
生产者
import java.util.Queue;
import java.util.Random;
public class Producer implements Runnable{
private Queue<Integer> queue;
private int maxSize;
public Producer(Queue<Integer> queue, int maxSize){
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true){
synchronized (queue){
while (queue.size() == maxSize){
try{
System.out.println("Queue is Full");
queue.wait();
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
Random random = new Random();
int i = random.nextInt();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Produce " + i);
queue.add(i);
queue.notifyAll();
}
}
}
}
消费者
import java.util.Queue;
public class Consumer implements Runnable{
private Queue<Integer> queue;
private int maxSize;
public Consumer(Queue<Integer> queue, int maxSize){
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true){
synchronized (queue){
while (queue.isEmpty()){
System.out.println("Queue is Empty");
try{
queue.wait();
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
int v = queue.remove();
System.out.println("Consume " + v);
queue.notifyAll();
}
}
}
}
主函数
import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args){
Queue<Integer> queue = new LinkedList<>();
int maxSize = 10;
Producer p = new Producer(queue, maxSize);
Consumer c = new Consumer(queue, maxSize);
Thread pT = new Thread(p);
Thread pC = new Thread(c);
pT.start();
pC.start();
}
结果展示
Produce -394878318
Produce 220306153
Produce 1700625372
Produce -31258783
Produce 1320552609
Produce -1430725702
Produce -502350663
Produce 105676351
Produce 1591516646
Produce 361034485
Queue is Full
Consume -394878318
Consume 220306153
Consume 1700625372
Consume -31258783
Consume 1320552609
Consume -1430725702
Consume -502350663
Consume 105676351
Consume 1591516646
Consume 361034485
Queue is Empty
Produce 806263710
Consume 806263710
Queue is Empty