概述
java也是支持多线程的,因为java是oop语言,所以java中的多线程就是操作java库中的Thread类以及它的API。
创建方式
- 新建继承自定义类然后继承Thread类,重写里面的
run()
方法,最后调用start()
方法。 - 新建一个类去实现Runnable接口,实现
run()
方法,然后new Thread(Runnable r)
创建一个Thread对象,调用start()
方法。 - 新建一个类去实现Callable接口,实现
run()
方法,然后new FutureTask<>(Callable c)
创建一个FutureTask对象,再然后new Thread(FutureTask f)
创建一个Thread对象,调用start()
方法。
demo示例如下:
/**
* @Classname CreateThread
* @Date 2020/11/22 13:00
* @author hzq
*/
package com.hzq.create;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方式一
MyThread1 thread1 = new MyThread1();
thread1.start();
//方式二
Thread thread2 = new Thread(new MyThread2());
thread2.start();
//方式三
FutureTask<Boolean> task = new FutureTask<>(new MyThread3());
Thread thread3 = new Thread(task);
thread3.start();
Boolean b = task.get();
System.out.println(b);
}
}
//方式1
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("使用继承Thread的方式创建线程");
}
}
//方式2
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable的方式创建线程");
}
}
//方式3
class MyThread3 implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
System.out.println("实现Callable的方式创建线程");
return true;
}
}
ps:开发时不要使用第一种。不要调用run()方法,当我们new 一个Thread对象时,其实就是创建了一个线程,只有当调用了start()方法时,这个线程才会进入就绪状态,然后等待cpu调度获取执行权,调用run()方法相当于调用一个普通类的一个普通方法。方式三可以有返回值。
API
在前面的3种创建方式中可以看出,java对多线程的操作,其实就是学会使用Thread类的各种API接口。下面会简单介绍几个常用的方法,有兴趣的可以去查一下jdk文档。
- sleep方法,使线程阻塞一定的时间
demo示例:
/**
* @Classname TestSleep
* @Date 2020/11/22 13:24
* @author hzq
*/
package com.hzq.api;
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
for (int i = 0;i<10000;i++){
System.out.println(i);
}
//当程序运行到这里,会阻塞5秒,然后继续执行.
Thread.sleep(5000);
System.out.println("线程结束");
}
}
- yield方法,使线程让出cpu,然后与其他线程重新竞争cpu的执行权。(有可能没有效果)
demo示例:
/**
* @Classname TestYield
* @Date 2020/11/22 13:35
* @author hzq
*/
package com.hzq.api;
public class TestYield {
public static void main(String[] args) throws InterruptedException {
new Thread(new Yield1(),"线程一").start();
Thread.sleep(10);
new Thread(new Yield2(),"线程二").start();
}
}
class Yield1 implements Runnable{
@Override
public void run() {
for (int i = 0;i < 1000; i++){
System.out.println(Thread.currentThread().getName()+"------->开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"------->结束执行");
}
}
}
class Yield2 implements Runnable{
@Override
public void run() {
for (int i = 0;i < 1000; i++){
System.out.println(Thread.currentThread().getName()+"------->开始执行");
System.out.println(Thread.currentThread().getName()+"------->结束执行");
}
}
}
ps:可以把Thread.yield()注释掉看看输出效果,对比一下就可以看出来yield方法的效果了
- join方法,强制获取cpu权限,阻塞其他的线程,直到自身结束。
demo示例:
/**
* @Classname TestJoin
* @Date 2020/11/22 13:51
* @author hzq
*/
package com.hzq.api;
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Join1(), "线程一");
thread.start();
for (int i = 0; i < 500; i++) {
if (i == 200){
thread.join();
}
System.out.println("main线程---->"+i);
}
}
}
class Join1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
ps:可以将thread.join()方法注释掉看看输出效果。
4. setPriority方法,设置线程的优先级,从一到十,线程开启之后设置无效,必须在线程开启之前设置,默认是5,线程的优先级只是权重的大小,并不代表优先级高的一定比优先级低的先执行,更像是两个人买彩票,甲买了100张彩票,已买了1张,那么甲中奖的概率一定大于已。
demo示例:
/**
* @Classname TestPriority
* @Date 2020/11/22 14:11
* @author hzq
*/
package com.hzq.api;
//测试线程优先级
public class TestPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(new Priority(),"优先级1");
thread1.setPriority(1);
Thread thread2 = new Thread(new Priority(),"优先级4");
thread2.setPriority(2);
Thread thread3 = new Thread(new Priority(),"优先级6");
thread3.setPriority(6);
Thread thread4 = new Thread(new Priority(),"优先级10");
thread4.setPriority(10);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class Priority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---->执行");
}
}
- setDaemon()方法,设置该线程为守护线程。jvm只会关心用户线程是否执行结束,不会操心守护线程,一旦用户线程全部执行完毕后,jvm就会自动退出,不会管守护线程是否执行结束。
demo示例:
/**
* @Classname TestDaemon
* @Date 2020/11/22 14:22
* @author hzq
*/
package com.hzq.api;
//测试守护线程
public class TestDaemon {
public static void main(String[] args) {
Thread daemon = new Thread(new Daemon(), "守护线程");
Thread user = new Thread(new UserThread(), "用户线程");
//设置该线程为守护线程
daemon.setDaemon(true);
daemon.start();
user.start();
}
}
class Daemon implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class UserThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"----->"+i);
}
}
}
ps:可以试着将true改成false。
线程同步
概述
多线程中最大的问题就是线程的同步问题,比如两个线程同时调用一台打印机打印东西,就有可能会出现问题。同样的,当多个线程同时操作一个共享变量时,也会有概率发生错误。线程同步的基本原理其实就是让临界资源同时只能被一个线程所操作。java中的线程同步大部分是使用synchronized关键字来进行,在java5中引进了Lock类,现在Lock类的使用优先级要比synchronized关键字更高,也就是说推荐使用Lock类。
1. synchronized关键字
- 用法
synchronized关键字可以用在方法上以及代码块上 - 原理
当synchronized关键字用在代码块上时,需要传入一个任意对象来当锁,当多个线程同时调用该代码块时,需要先去获取锁对象,只有持有锁对象的线程,才能去调用该代码块,否则只能阻塞等待持有锁对象的线程执行完这个代码块后释放锁对象,然后再进行竞争成为锁对象的持有者。
当synchronized关键字用在普通方法上时,锁对象是this,即当前对象,如果该方法是静态方法时,锁对象是当前对象的Class对象。
demo示例:
/**
* @Classname TestSynchronized
* @Date 2020/11/22 14:47
* @author hzq
*/
package com.hzq.synchronous.synchronizedfont;
//测试synchronized关键字
public class TestSynchronized {
public static void main(String[] args) {
TicketOffice ticketOffice = new TicketOffice();
Thread thread1 = new Thread(ticketOffice, "售票厅一");
Thread thread2 = new Thread(ticketOffice, "售票厅二");
Thread thread3 = new Thread(ticketOffice, "售票厅三");
Thread thread4 = new Thread(ticketOffice, "售票厅四");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class TicketOffice implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket <= 0){
break;
}
sellTicket();
}
}
public synchronized void sellTicket(){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"售出第"+ticket+"张票");
ticket--;
}
}
}
ps: 将synchronized关键字去掉再运行看看结果有什么区别。
2. Lock类
- 简介
Lock锁是java5出现的一种同步机制,它拥有比synchronized关键字更加优秀的性能,以及更加清晰的结构,使用方法也很简单,只需要在需要同步的代码块之前调用lock()
方法,离开代码块要调用unlock()
方法即可。
demo示例:
/**
* @Classname TestSynchronized
* @Date 2020/11/22 14:47
* @author hzq
*/
package com.hzq.synchronous.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//测试synchronized关键字
public class TestLock {
public static void main(String[] args) {
TicketOffice ticketOffice = new TicketOffice();
Thread thread1 = new Thread(ticketOffice, "售票厅一");
Thread thread2 = new Thread(ticketOffice, "售票厅二");
Thread thread3 = new Thread(ticketOffice, "售票厅三");
Thread thread4 = new Thread(ticketOffice, "售票厅四");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class TicketOffice implements Runnable{
private static int ticket = 100;
private static Lock lock =new ReentrantLock();
@Override
public void run() {
while (true){
if (ticket <= 0){
break;
}
sellTicket();
}
}
public void sellTicket(){
lock.lock();
try {
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"售出第"+ticket+"张票");
ticket--;
}
}finally {
lock.unlock();
}
}
}
线程池
- 概述
频繁的创建线程,线程执行结束后销毁线程都是十分耗费资源的,所以出现了线程池技术,它类似于数据库的数据库连接池。当需要使用多线程去执行程序的时候从线程池中拿出已经创建好的线程,等用完以后再将线程放回去。 - 使用方法
通过Executors类的静态方法newFixedThreadPool()
方法创建一个线程池,然后使用execute()
方法去执行Runnable类,使用submit()
方法去执行Callable类。
demo示例:
/**
* @Classname TestThreadPool
* @Date 2020/11/22 15:18
* @author hzq
*/
package com.hzq.ThreadPool;
import java.util.concurrent.*;
public class TestThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//2.执行Runnable
pool.execute(new MyThread2());
//3.执行Callable
Future<Boolean> future = pool.submit(new MyThread3());
System.out.println(future.get());
pool.shutdownNow();
}
}
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable的方式创建线程");
}
}
//方式3
class MyThread3 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("实现Callable的方式创建线程");
return true;
}
}