目录
同步代码块和同步函数的区别?
线程:进程中一个负责程序运行的控制单元。执行路径
一个进程中至少要有一个线程。
为了同时运行多部分代码,每个线程要执行的任务。
多线程程的好处:解决了多部分同时运行;
多线程的弊端:线程太多会导致效率降低;
应用程序的执行都是CPU 在做着快速切换,这种切换是随机的。
执行 main 函数的线程:
改线程的任务代码都定义在main 函数里面。
创建方式一
继承Thread类
1. 子类覆盖父类中的run方法,将线程运行
的代码存放在run中。
2. 建立子类对象的同时线程也被创建。
3. 通过调用start方法开启
为什么要覆盖run 方法呢?
Thread 类用于描述线程,线程是需要任务的,所以 Thread 类也是需要任务的描述。
这个任务是通过run 方法实现的。 也就是run方法是封装自定义线程运行任务的函数。
run 方法里面是要运行的任务代码。
如果直接调用run 方法 跟普通函数调用没有区别不会开启新线程。
class Demo extends Thread{}
eg: Demo1 d1= new Demo();
Demo2 d2 =new Demo()
d1.run();
d2.run();
*****需要调用 d1.start() 才会启动线程。 java 虚拟机调用该线程的run 方法。
- start() 方法 只能调用一次。
- 多线程的弊端:
new Thread(){//创建线程 方法1
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("Thread="+Thread.currentThread().getName() +i);
}
}
}.start();
线程的四种状态:
创建 运行 冻结 消亡
创建线程方式二
实现Runnable接口
1. 子类覆盖接口中的run方法。
2. 通过Thread类创建线程,并将实现了
Runnable接口的子类对象作为参数传递给
Thread类的构造函数。
3. Thread类对象调用start方法开启线程。
思考:为什么要给Thread类的构造函数传递
Runnable的子类对象
Runnable r= new Runnable() {//创建线程方法2
@Override
public void run() {
for(int x=0;x<10;x++){
System.out.println("Runable "+Thread.currentThread().getName());
}
}
};
new Thread(r).start();
创建方式3:Callable Future
https://www.cnblogs.com/felixzh/p/6044371.html 详细
线程安全问题
导致安全问题的出现的原因:
多个线程访问出现延迟。
线程随机性 。
注:线程安全问题在理想状态下,不容易出
现,但一旦出现对软件的影响是非常大
的。
线程同步synchronized
格式:
synchronized(对象)
{
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。
该对象如同锁的功能。
执行流程:0线程 首先 获得obj ,并且将1置位为0 ,Thread.sleep 这时 0线程 放弃cpu执行权,线程1,2,3 判断 obj 对象为0 ,进不来,Thread.sleep结束后继续执行。 线程0 将obj 置位1 。
线程同步的特点
同步的前提:
同步需要两个或者两个以上的线程。
多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断
同步上的锁,这是很耗费资源的,无形
中会降低程序的运行效率。
如下使用不是同一个锁
package thread;
class Ticket_3 implements Runnable{
private int num = 100;
public void run()
{
Object obj = new Object();// 每个线程都有一个锁 不是同一个锁
while(true)
{
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
}
}
}
}
}
public class Ticket3 {
public static void main(String[] args) {
Ticket_3 t = new Ticket_3();//创建一个线程任务对象。
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();
}
}
依然存在线程安全问题:
使用同一个锁;
package thread;
class Ticket_3 implements Runnable{
private int num = 100;
Object obj = new Object();// 每个线程都有一个锁 不是同一个锁
public void run()
{
while(true)
{
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
}
}
}
}
}
public class Ticket3 {
public static void main(String[] args) {
Ticket_3 t = new Ticket_3();//创建一个线程任务对象。
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();
}
}
/*
Ticket t1 = new Ticket();
// Ticket t2 = new Ticket();
// Ticket t3 = new Ticket();
// Ticket t4 = new Ticket();
t1.start();
t1.start();//一个线程不能开启两次,会抛出无效线程状态异常
t1.start();
t1.start();
*/
银行存钱: 两个人存钱 ,每个存100,存300次。
package thread;
class Bank{
private int sum;
Object obj =new Object();
public void add(int num ){
synchronized (obj) {
sum += num;
System.out.println("sum =" + sum);
}
}
}
class Cus implements Runnable {
Bank bank =new Bank();
@Override
public void run() {
for(int i=0;i<3;i++){
bank.add(100);
}
}
}
public class BankDemo {
public static void main(String[] args) {
Cus cus =new Cus();
Thread thread =new Thread(cus);
Thread thread1 =new Thread(cus);
thread.start();
thread1.start();
}
}
同步函数
格式:
在函数上加上synchronized修饰符即可。
思考:同步函数用的是哪个锁呢?
线程间通信
思考1: wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
1,这些方法存在与同步中。
2,使用这些方法时必须要标识所属的同步的锁。
3,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
思考2: wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
停止线程
1. 定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即
可。
2. 使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到
运行状态中来。
注: stop方法已经过时不再使用。
线程类的其他方法
setPriority(int num)
setDaemon(boolean b)
join()
自定义线程名称
toString()
多线程的好处:解决了多部分同时运行
JVM 多线程解析:
-
对象怎么被回收的?
定义在Object 类中的finalize ,当垃圾回收器确定不存在改对象的更多引用时,由对象的垃圾回收器做垃圾回收。
子类重写 finalize 方法以便配置系统资源或者执行其他清除。
什么是垃圾回收器?执行垃圾回收的程序 :
System 类下的 gc 方法:
垃圾回收器 gc 方法的调用及 可能出现的结果?
package thread;
/**
* 调用 gc() 的时候 垃圾回收器会不定时开始
* 垃圾回收,主线程继续执行。
* Created by zengjx on 2019/4/29.
*/
class Demo6{
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println(" Demo6 finalize");
}
}
public class ThreadDemo6 {
public static void main(String[] args){
new Demo6();
new Demo6();
new Demo6();
System.gc();//开启新线程 垃圾回收器
System.out.println(" main ");
}
}
/**
* main
Demo6 finalize
Demo6 finalize
Demo6 finalize
*/
/**
* Demo6 finalize
Demo6 finalize
Demo6 finalize
main
*/
// 垃圾回收器 收了一个后主线程结束 ,虚拟机准备结束退出了
//内存被强制清除
/**
* 可能出现 垃圾回收器 回收一个后虚拟机准备结束强制清除所在内存区域
* 还没等 运行下一个finalize 就已经结束了。
* main
*Demo6 finalize
*/
//主线程 结束了 虚拟机其他线程还在运行 ,虚拟机还没结束。
-
final finally finalize 三者区别?
一、final :
1、修饰符(关键字) 如果一个类被声明为final,意味着它不能再派生新的子类,不能作为父类被继承。因此一个类不能既被声明为abstract,又被声明为final的。
2、将变量或方法声明为final,可以保证他们使用中不被改变。被声明为final的变量必须在声明时给定初值,而以后的引用中只能读取,不可修改,被声明为final的方法也同样只能使用,不能重载。
二、finally:
在异常处理时提供finally块来执行清除操作。如果抛出一个异常,那么相匹配的catch语句就会执行,然后控制就会进入finally块,如果有的话。
三、finalize:
是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。这个方法是在垃圾收集器在确定了,被清理对象没有被引用的情况下调用的。
finalize是在Object类中定义的,因此,所有的类都继承了它。子类可以覆盖finalize()方法,来整理系统资源或者执行其他清理工作。
多线程主函数运行实例
main 进栈------d1.show() 进栈---d1,show() 出栈---d2.show()进栈---d2.show()出栈- main 函数出栈。
- 多线程的弊端;线程的安全问题
多线程执行时随机的,例如卖票可能出现负数 。
package exception;
public class Ticket extends Thread {
private static int num =100;
Object object =new Object();
@Override
public void run() {
super.run();
while(true){
synchronized (object){
try{
Thread.sleep(10);
}catch ( InterruptedException e){
e.printStackTrace();
}
System.out.println(" name+ " +Thread.currentThread()+" num="+num);
num--;
if(num<0){
System.out.println(" ");
break;
}
}
}
}
}
class TicktetDemo{
public static void main(String[] args) {
Ticket ticket =new Ticket();
Ticket ticket2 =new Ticket();
Ticket ticket3 =new Ticket();
ticket.start();
ticket2.start();
ticket3.start();
}
}
输出结果:
name+ Thread[Thread-0,5,main] num=4
name+ Thread[Thread-2,5,main] num=4
name+ Thread[Thread-1,5,main] num=1
name+ Thread[Thread-2,5,main] num=1
name+ Thread[Thread-0,5,main] num=1
name+ Thread[Thread-1,5,main] num=-2
线程安全问题的原因是什么?
多线程调用是随机的,当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算就会导致线程安全问题。
-
线程同步代码块
- 解决方法: 多个线程在操作共享数据的的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不能够参与运算。
必须要当前线程把这些代码都执行完之后,其他线程才可以参与运算。
package thread;
class Bank{
private int sum;
Object obj =new Object();
public synchronized void add(int num ){//同步函数 用 sychronized
// synchronized (obj)
{
sum += num;
System.out.println("sum =" + sum);
}
}
}
class Cus implements Runnable {
Bank bank =new Bank();
@Override
public void run() {
for(int i=0;i<3;i++){
bank.add(100);
}
}
}
public class BankDemo {
public static void main(String[] args) {
Cus cus =new Cus();
Thread thread =new Thread(cus);
Thread thread1 =new Thread(cus);
thread.start();
thread1.start();
}
}
同步锁
明白什么该同步,什么不该同步?
run 方法设置为同步函数。 会导致 只有一个线程获得锁直到票卖完。
应该要把需要同步的 封装起来
package thread;
class Ticket implements Runnable{
private int num =100;
@Override
public synchronized void run() {// run 设置为同步函数
while (true){
if(num>0){
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(" thread name"+Thread.currentThread() +"num ="+num--);
}
}
}
}
public class SynFunctionLockDemo {
public static void main(String[] args) {
Ticket ticket =new Ticket();
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
Thread thread3 =new Thread(ticket);
Thread thread4 =new Thread(ticket);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
封装需要同步的代码。
package thread;
class Ticket implements Runnable{
private int num =100;
@Override
public void run() {
while (true){
int ret= show();
if(ret>0){
System.out.println(" game over");
return;
}
}
}
//封装函数
public synchronized int show(){
if(num>0){
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(" thread name"+Thread.currentThread() +"num ="+num--);
}
if(num==0){
return 1;
}
return 0;
}
}
public class SynFunctionLockDemo {
public static void main(String[] args) {
Ticket ticket =new Ticket();
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
Thread thread3 =new Thread(ticket);
Thread thread4 =new Thread(ticket);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
同步代码块与同步函数的同步问题:
问题代码:
package thread;
class Ticket implements Runnable{
private int num =100;
boolean flag=true;
Object object =new Object();
@Override
public void run() {
if(flag==true){
while (true){
synchronized (object){
if(num>0){
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(" thread name"+Thread.currentThread() +"obj ="+num--);
}
}
}
}else{
while (true){
this.show();
}
}
}
//封装函数
public synchronized int show(){
if(num>0){
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(" thread name"+Thread.currentThread() +"num ="+num--);
}
if(num==0){
return 1;
}
return 0;
}
}
public class SynFunctionLockDemo {
public static void main(String[] args) {
Ticket ticket =new Ticket();
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
thread1.start();
ticket.flag=false;
thread2.start();
}
}
标志位flag初始化为 true 但是main 函数中里面的语句会一起 执行此时flag 会被置为flase, start()运行的时候只会 this.show();
main 函数添加 Thread.sleep(10) :
public class SynFunctionLockDemo {
public static void main(String[] args) {
Ticket ticket =new Ticket();
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
thread1.start();
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
ticket.flag=false;
thread2.start();
}
}
此时:原因是:不是用的用一个锁
修改: 使用 synchronized (this),因为 public synchronized int show() 调用也是当前对象 同步函数使用的锁是this,这样可以保证使用同一个锁。
package thread;
class Ticket implements Runnable{
private int num =100;
boolean flag=true;
// Object object =new Object();
@Override
public void run() {
if(flag==true){
while (true){
synchronized (this){//当前对象
if(num>0){
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(" thread name"+Thread.currentThread() +"obj ="+num--);
}
}
}
}else{
while (true){
this.show();
}
}
}
//封装函数
public synchronized int show(){
if(num>0){
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(" thread name"+Thread.currentThread() +"num ="+num--);
}
if(num==0){
return 1;
}
return 0;
}
}
public class SynFunctionLockDemo {
public static void main(String[] args) {
Ticket ticket =new Ticket();
Thread thread1 =new Thread(ticket);
Thread thread2 =new Thread(ticket);
thread1.start();
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
ticket.flag=false;
thread2.start();
}
}
-
同步函数与同步代码块有什么区别?
1.同函数的锁唯一的 是this ,而同步代码块的锁可以是任意对象,建议使用 同步代码块。