Java 多线程
一、线程概述
进程直译:正在进行中的程序
线程
线程就是进程中一个负责程序执行的控制单元(执行路径) 一个进程中可以有多个执行路径,称为多线程。开启多个线程是为了同时运行多部分代码。 一个进程中至少有一个线程。每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
二、多线程好处与弊端
好处:解决了多部分同时运行的问题
弊端:线程太多会导致效率降低
其实应用程序的执行都是 cpu 在做着快速切换完成的。这个切换是随机的。
三、JVM 中多线程的解析
JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。 1、执行main函数的线程 该线程的任务代码都定义在main函数中。 2、负责垃圾回收的线程
四、线程的创建方式-Thread类
创建新执行线程有两种方法:
第一种方法是将类声明为 Thread 的子类。该子类重写 Thread 的 run 方法
class Demo extend Thread{
private String name;
Demo(name){
this.name = name;
}
public void run(){
this.show();
}
public void show(){
for(int x = 0;x<10;x++){
System.out.println(name+"...x="+x);
}
}
}
class ThreadDemo{
public static void main(){
//创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行
//而运行的指定代码就是这个执行路径的任务
//jvm创建的主线程的任务都定义在了主函数中。
//而自定义的线程的任务在哪呢?
//Thread类用于描述线程,线程是需要任务的,所以Thread类也是对任务的描述
//这恶搞任务就是通过Tnread的run方法体现,也就是说run方法就是封装自定义线程运行任务的函数。
//run方法中定义的就是线程要运行的任务代码
//开启线程是为了运行指定代码,所以只有继承Thread类,并覆写run方法
//将运行的代码定义在run方法中即可。
Demo d1 = new Demo("旺财");
Demo d2 = new Demo("xiaoqiang");
d1.start();//开启线程后,jvm会自动调用该线程的run方法
d2.start();//不用自己手动调用run方法
}
}
第二种方法是实现 Runnable 接口
class Demo implements Runnable{//准备扩展Deno类的功能,让其中的内容可以作为线程的任务执行,通过接口的形式完成
private String name;
Demo(name){
this.name = name;
}
public void run(){
this.show();
}
public void show(){
for(int x = 0;x<10;x++){
System.out.println(name+"...x="+x);
}
}
}
class ThreadDemo{
public static void main(){
Demo d1 = new Demo();//这时候不是线程对象,不能调用start
Thread t1 = new Thread(d1);//线程对象
Thread t2 = new Thread(d1);
t1.start();
t2.start();
}
}
第二种方法的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装(按照面向对象的思想将任务封装成对象)
- 避免了 java 单继承的局限性
所以常见线程的第二种方式比较常用。
五、Thread 类中的方法 - 线程名称
可以通过 Thread 的 getName 获取现场的名称 Thread-编号(从0开始)
class Demo extend Thread{
private String name;
Demo(name){
this.name = name;
}
public void run(){
this.show();
}
public void show(){
for(int x = 0;x<10;x++){
//线程以创建,名字就有了
System.out.println(name+"...x="+x+"ThreadName="+this.getName());
//获取当前运行线程的名字
System.out.println(name+"...x="+x+"ThreadName="+Thread.currentThread().getName());
}
}
}
六、多线程运行内存图解
七、线程的四种状态
cpu 的执行资格:可以被 cpu 处理,在处理队列中排队。
cpu 的执行权:正在被 cpu 处理。
运行状态的线程具备 cpu 的执行资格和 cpu 的执行权 ,临时阻塞状态的线程具备 cpu 的执行资格,但不具备 cpu 的执行权 冻结状态的线程,不具备 cpu 的执行资格和 cpu 的执行权。
八、卖票示例
class Ticket implements Runnable{
private int num = 100;
public void run(){
while(true){
if(num>0){
System.out.println(Thread.currentThread().getName()+"...sale..."+num--)
}
}
}
}
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
九、多线程安全问题
class Ticket implements Runnable{
private int num = 100;
public void run(){
while(true){
if(num>0){
try{//由于线程的安全问题,可能会导致负数票的出现
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
}catch{}
}
}
}
}
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
线程安全问题产生的原因
- 多个线程在操作共享的数据。
- 操作共享数据的代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
十、解决多线程问题-同步代码块
解决思路:就是将多条操作共享数据的线程封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在 java 中同步代码块就可以解决这个问题,同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
修改代码如下:
class Ticket implements Runnable{
private int num = 100;
Object obj = new Object();//创建一个对象为同步代码块使用
public void run(){
while(true){
synchronized(obj){//同步代码块
if(num>0){
try{
System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
}catch{}
}
}
}
}
}
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步代码块的好处与弊端
- 同步的好处:解决了线程安全问题
- 同步的弊端:相对以前降低了效率,因为同步外的线程都会判断同步锁。
- 同步的前提:必须有多个线程必须使用同一个锁。
十一、解决多线程问题-同步函数
class Back{
private int sum;
private Object obj = new Object();
public void add(int num){
synchronized(obj){//同步代码块
sum = sum+num;
try{Thread.sleep(10)}catch(InterruptedExpection e){}
System.out.println("sum="+sum);
}
}
//同步的第二种表现形式
public void synchronized add(int num){//同步函数 (只需要用synchronized关键字修饰即可,不用使用锁)
sum = sum+num;
try{Thread.sleep(10)}catch(InterruptedExpection e){}
System.out.println("sum="+sum);
}
}
class Cus implements Runnable{
private Bank b = new Bank();
public void run(){
for(int x=0;x<3;x++){
b.add(100);
}
}
}
使用同步代码块时候的锁是自己 new 的一个对象,那么同步函数的锁是什么呢?(this)
建议使用同步代码块
静态同步函数用的锁就不是this了。当前的字节码对象 (this.getClass()/类名.class)
十二、单例模式涉及的多线程问题
//饿汉式(不存在线程安全问题)
class Single{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return this.s;
}
}
//懒汉式(存在安全问题需要解决)
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s==null){
s = new Single();
}
return this.s;
}
//解决线程安全
public static Single getInstance(){
if(s==null){
synchronized(Single.class/this.getClass()){
if(s==null){
s = new Single();
}
}
}
return this.s;
}
}
十三、死锁
死锁的出现之一:同步的嵌套
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.locka){
System.out.println("if locka...");
synchronized(MyLock.lockb){
System.out.println("if lockb...");
}
}
}else{
synchronized(MyLock.lockb){
System.out.println("else lockb...");
synchronized(MyLock.locka){
System.out.println("else locka...");
}
}
}
}
}
class MyLock{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest{
public static void main(){
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论