多线程编程
1、多线程
1、什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
2、多线程的作用?
提高效率
3、多线程的应用场景
只要你想让多个事情同时运行就需要用到多线程
比如:软件中的好事操作、所有的聊天软件、所有的服务器
2、并发与并行
1、并发:
在同一个时刻,有多个指令在单个CPU交替执行
2、并行:
在同一时刻,有多个指令在多个CPU同时执行
3、多线程第一种实现方式:继承Thread类的方式
package thread01;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 1000; i++) {
System.out.println(getName()+"Hello ");
}
}
}
package thread01;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第一种启动方式
* 1、自己定义一个类继承Thread
* 2、重写里面的run方法
* 3、创建子类的对象,并启动线程
*
*/
MyThread t1=new MyThread();
MyThread t2=new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
4、第二种方式:实现Runnable接口
package thread02;
import static java.lang.Character.getName;
public class MyThread implements Runnable{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 1000; i++) {
//获取当前线程对象
Thread t=Thread.currentThread();
System.out.println(t.getName()+"Hello ");
}
}
}
package thread02;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 第二种创建方式
* 1、自己定义一个类实现Runnable接口
* 2、重写里面的run方法
* 3、创建自己的类的对象
* 4、创建一个Thread类的对象,并开启线程
*/
MyThread t1=new MyThread();
Thread t11=new Thread(t1);
Thread t12=new Thread(t1);
t11.setName("线程1");
t12.setName("线程2");
t11.start();
t12.start();
}
}
5、第三种实现方式:利用Callable接口和Future接口实现
package thread03;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//书写线程要执行的代码
int sum=0;
for (int i = 0; i < 1000; i++) {
//获取当前线程对象
Thread t=Thread.currentThread();
System.out.println(t.getName()+"Hello ");
sum+=i;
}
return sum;
}
}
package thread03;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 第三种方式:
* 特点:可以获取多线程运行结果
* 1、创建一个类MyCallable实现Callable接口
* 2、重写call(是有返回值的,表示多线程运行的结果)
* 3、创建MyCallable对象(表示多线程要执行的任务)
* 4、创建FutureTask对象(作用:管理多线程运行的结果)
* 5、创建Thread类的对象,并启动(表示线程)
*/
MyCallable mc=new MyCallable();
FutureTask ft=new FutureTask(mc);
Thread t1=new Thread(ft);
t1.start();
System.out.println(ft.get());
}
}
总结:
6、多线程常用方法
package threadmethod;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//获取当前线程对象
Thread t=Thread.currentThread();
System.out.println(getName()+"@"+i);//getName获取线程名称
}
}
}
package threadmethod;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/**
*setName(String name)
* 1、线程默认有名字;格式Thread-X(0,1,2,3...)
* 2、如果给线程设置名字,两种方式:使用set方法,或者使用构造方法进行设置
*
* static Thread currentThread()
* 细节:
* 当jvm虚拟机启动之后,会自动的启动多条线程
* 其中有一条线程就叫做main线程
* 他的作用就是去调用main方法,并执行里面的代码
* 在以前,我们所写的所有代码,其实都是运行在main线程当中
*
* static void sleep(long time) 让线程休眠指定的时间(单位:毫秒)
* 细节:
* 1、哪条线程执行到这个方法,哪条线程就会停留在这里对应的时间
* 2、方法的参数就表示休眠的时间
* 3、时间到了之后,线程会自动醒来
*/
MyThread t1=new MyThread("坦克");
MyThread t2=new MyThread("飞机");
t1.start();
t2.start();
// System.out.println(Thread.currentThread().getName());//获取当前线程对象
// System.out.println("11111111");
// Thread.sleep(5000);
// System.out.println("22222222");
}
}
7、线程的优先级
package threadpriority;
public class MyBunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
package threadpriority;
public class ThreadDemo {
public static void main(String[] args) {
/**
*
* setPriority(int newPriority) 设置线程的优先级
* final int getPriority() 获取线程的优先级
*/
MyBunnable myBunnable = new MyBunnable();
Thread thread1 = new Thread(myBunnable,"飞机");
Thread thread2 = new Thread(myBunnable,"坦克");
thread1.setPriority(1);
thread2.setPriority(10);
System.out.println(thread1.getPriority());
System.out.println(thread2.getPriority());
System.out.println(Thread.currentThread().getPriority());
thread1.start();
thread2.start();
}
}
8、守护线程(备胎线程)
void setDaemon(bool on)
当非守护线程执行完毕之后,守护线程会陆续结束
9、出让线程/礼让线程
10、插入线程
package thread01;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"@"+i);
}
}
}
package thread01;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/**
* public final void join() 插入线程、插队线程
*
*/
MyThread t1=new MyThread();
t1.setName("线程1");
t1.start();
t1.join();//将线程1插入到main线程之前,当线程1执行完之后才执行main线程
for (int i = 0; i < 10; i++) {
System.out.println("main线程:"+i);
}
}
}
11、线程的生命周期
12、线程的安全问题
1、同步代码块
package instance1;
public class MyThread extends Thread{
static int tickets=0;//表示所有这个类的对象,都共享ticket数据
static Object obj=new Object();//锁对象,一定是唯一的
@Override
public void run() {
while (true){
//同步代码块
synchronized (obj){
if (tickets<100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
tickets++;
System.out.println(getName()+"正在卖第"+tickets+"张票");
}else{
break;
}
}
}
}
}
package instance1;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 需求:电影院卖票:共100张票,3个窗口卖票。设计多线程程序卖票。
*/
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
D:\software\jdk\bin\java.exe "-javaagent:D:\software\IntelliJ IDEA 2023.3.2\lib\idea_rt.jar=56272:D:\software\IntelliJ IDEA 2023.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\MyThread\com.thread\target\classes instance1.ThreadDemo
窗口1正在卖第1张票
窗口1正在卖第2张票
窗口1正在卖第3张票
窗口1正在卖第4张票
窗口1正在卖第5张票
窗口1正在卖第6张票
窗口1正在卖第7张票
窗口1正在卖第8张票
窗口1正在卖第9张票
窗口1正在卖第10张票
窗口1正在卖第11张票
窗口1正在卖第12张票
窗口1正在卖第13张票
窗口1正在卖第14张票
窗口1正在卖第15张票
窗口1正在卖第16张票
窗口1正在卖第17张票
窗口1正在卖第18张票
窗口1正在卖第19张票
窗口1正在卖第20张票
窗口1正在卖第21张票
窗口1正在卖第22张票
窗口1正在卖第23张票
窗口1正在卖第24张票
窗口1正在卖第25张票
窗口1正在卖第26张票
窗口1正在卖第27张票
窗口1正在卖第28张票
窗口1正在卖第29张票
窗口1正在卖第30张票
窗口1正在卖第31张票
窗口1正在卖第32张票
窗口1正在卖第33张票
窗口1正在卖第34张票
窗口1正在卖第35张票
窗口1正在卖第36张票
窗口1正在卖第37张票
窗口1正在卖第38张票
窗口1正在卖第39张票
窗口1正在卖第40张票
窗口1正在卖第41张票
窗口1正在卖第42张票
窗口1正在卖第43张票
窗口1正在卖第44张票
窗口1正在卖第45张票
窗口1正在卖第46张票
窗口1正在卖第47张票
窗口1正在卖第48张票
窗口1正在卖第49张票
窗口1正在卖第50张票
窗口1正在卖第51张票
窗口1正在卖第52张票
窗口1正在卖第53张票
窗口1正在卖第54张票
窗口1正在卖第55张票
窗口1正在卖第56张票
窗口1正在卖第57张票
窗口1正在卖第58张票
窗口1正在卖第59张票
窗口1正在卖第60张票
窗口1正在卖第61张票
窗口1正在卖第62张票
窗口1正在卖第63张票
窗口1正在卖第64张票
窗口1正在卖第65张票
窗口1正在卖第66张票
窗口1正在卖第67张票
窗口1正在卖第68张票
窗口1正在卖第69张票
窗口1正在卖第70张票
窗口1正在卖第71张票
窗口1正在卖第72张票
窗口1正在卖第73张票
窗口1正在卖第74张票
窗口1正在卖第75张票
窗口1正在卖第76张票
窗口1正在卖第77张票
窗口1正在卖第78张票
窗口1正在卖第79张票
窗口1正在卖第80张票
窗口3正在卖第81张票
窗口3正在卖第82张票
窗口3正在卖第83张票
窗口3正在卖第84张票
窗口3正在卖第85张票
窗口3正在卖第86张票
窗口3正在卖第87张票
窗口3正在卖第88张票
窗口3正在卖第89张票
窗口3正在卖第90张票
窗口3正在卖第91张票
窗口3正在卖第92张票
窗口3正在卖第93张票
窗口2正在卖第94张票
窗口2正在卖第95张票
窗口2正在卖第96张票
窗口2正在卖第97张票
窗口3正在卖第98张票
窗口3正在卖第99张票
窗口3正在卖第100张票
Process finished with exit code 0
注意:
1、锁对象一定是唯一的,如果是不同的锁(下图所述),则加锁毫无意义(等于没锁)
错误示范:
正确示范:
2、同步方法
package instance2;
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
Thread thread3 = new Thread(myRunnable);
thread1.setName("窗口1");
thread2.setName("窗口2");
thread3.setName("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
package instance2;
public class MyRunnable implements Runnable{
int tickets=0;
@Override
public void run() {
/**
* 1、循环
* 2、同步代码块(同步方法)
* 3、判断共享数据是否到了末尾,到了末尾
* 4、没有到末尾
*
*/
while (true){
if (method()){
break;
}
}
}
public synchronized boolean method(){
if (tickets==100){
return true;
}else{
tickets++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets+"张票");
}
return false;
}
}
StringBuilder 和 StringBuffer 都是在 Java 中用于处理可变字符串的类。它们允许你在不创建新对象的情况下修改字符串内容,这对于性能优化非常有用,尤其是当你需要频繁地修改字符串时。
以下是 StringBuilder 和 StringBuffer 的主要区别:
线程安全性:
StringBuffer 的方法是同步的,这意味着它是线程安全的。你可以在一个多线程环境中安全地使用 StringBuffer 而不用担心数据一致性问题。
StringBuilder 的方法不是同步的,因此它是线程不安全的。在单线程环境中使用 StringBuilder 更加高效,因为它不需要同步所带来的额外开销。
性能:
因为 StringBuffer 的方法包含同步代码块,所以它的性能可能低于 StringBuilder,特别是在单线程应用中。
StringBuilder 没有同步开销,所以在单线程应用中通常比 StringBuffer 快。
引入时间:
StringBuffer 在早期版本的 Java 中就存在了。
StringBuilder 是在 Java 5(JDK 1.5)中引入的,作为 StringBuffer 的一个更高效的替代品,适用于单线程环境。
总结来说,如果你的应用是单线程的,或者你已经采取了其他措施来确保线程安全,那么你应该使用 StringBuilder 来获得更好的性能。如果你的应用是多线程的,并且需要在多个线程之间共享和修改字符串,那么你应该使用 StringBuffer 来确保线程安全。
选择哪个类取决于你的具体需求和应用程序的特性。
13、Lock锁
package lock1;
import instance1.MyThread;
public class ThreadDemo {
public static void main(String[] args) {
instance1.MyThread t1=new instance1.MyThread();
instance1.MyThread t2=new instance1.MyThread();
instance1.MyThread t3=new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package lock1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static Lock lock=new ReentrantLock();
static int tickets=0;
@Override
public void run() {
while (true){
lock.lock();//加锁
if(tickets==100){
break;
}else{
tickets++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets+"张票");
}
lock.unlock();//释放锁
}
}
}
14、死锁:是一个错误,不要去产生死锁
15-1、等待唤醒机制
package instancenotify01;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 需求:完成生产者和消费者(等待唤醒机制)的代码
* 实现线程轮流交替执行的效果
*/
Cook cook = new Cook();
Foodie foodie = new Foodie();
cook.setName("厨师");
foodie.setName("吃货");
cook.start();
foodie.start();
}
}
package instancenotify01;
public class Desk {
/**
* 作用:控制生产者和消费者的执行
*/
//是否有面条,0:没有,1:有
public static int foodFlag=0;
//总个数
public static int count=10;
//锁对象
public static Object lock=new Object();
}
package instancenotify01;
public class Cook extends Thread{
@Override
public void run() {
while (true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else{
if (Desk.foodFlag==1){
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
System.out.println("厨师做了一碗面条");
Desk.foodFlag=1;
Desk.lock.notifyAll();
}
}
}
}
}
}
package instancenotify01;
public class Foodie extends Thread{
@Override
public void run() {
/**
* 1、循环
* 2、同步代码块
* 3、判断共享数据是否(到了末尾)
* 4、没有到末尾
*/
while (true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else{
/**
* 1、先判断桌子上是否有面条
* 2、如果没有,就等待
* 3、如果有,就开吃
* 4、吃完之后,唤醒厨师继续做
* 5、吃的总数-1
* 6、修改桌子的状态
*/
if (Desk.foodFlag==0){
try {
Desk.lock.wait();//让当前线程与锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
Desk.count--;
System.out.println("吃货正在吃面条,还能再吃"+Desk.count+"碗!!!");
Desk.lock.notifyAll();
Desk.foodFlag=0;
}
}
}
}
}
}
15-2、阻塞队列
package instancenotify02;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
* 细节:
* 生产者和消费者必须使用同一个阻塞队列
*/
//创建阻塞队列
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<>(1);
//创建线程对象,并将阻塞队列传递过去
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
cook.start();
foodie.start();
}
}
package instancenotify02;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue queue){
this.queue=queue;
}
@Override
public void run() {
while (true){
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package instancenotify02;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue queue){
this.queue=queue;
}
@Override
public void run() {
while (true){
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
16、线程的状态
17、案例练习
1、
2、
3、
4、
5、
6、
7、
8、