进程,正在执行中的程序,比如,windows里任务管理器,里面的每一个就是一个进程,cpu在某个时间,只能执行一个程序,cpu在快速的切换,才导致各个程序在同时执行,
线程,是进程中的内容,每一个进程执行都有一个执行顺序,该顺序是一个执行路径或者叫控制单元。
进程:正在进行中的程序。只要启动,就会在内存空间中分配一块空间,而进程就是用于标识这个空间的,它用于封装里面的控制单元,其实进程就是一个应用程序运行时的内存分配空间。
线程:线程才是进程中执行的部分,就是进程中的一个独立的控制单元,线程在控制着进程的执行,一个进程中至少有一个线程,控制单元。
java VM ,启动的时候会有一个进程java.exe
该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程,目前用的是单线程
扩展,虚拟机启动的时候就是多线程,因为除了主线程还有一个线程在执行垃圾回收,
如何自定一个线程,
通过对api的查找,java已经提供了对线程这类事务的描述,就Thread类
创建线程的第一种方式,继承Thread 类,重写run方法
步骤:
1、定义类 ,继承Thread
2、复写Thread类中的run方法,目的 将自定义的代码存储在在run方法,让线程运行
3、调用线程的start的方法,该方法有两个作用,启动线程,调用run方法,开启线程并执行该线程的run方法
多线程在抢cpu资源,双核的cpu,内存就会成为瓶颈。我们会发现运行结果每一次都不同,因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)。cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为在互相抢夺cpu的执行权,这就是多线程的一个特性,随机性,谁抢到,谁执行,执行多长时间cpu说的算。
为什么要覆盖run方法呢
Thread类用户描述线程,该类就定义一个功能,用于存储线程要运行的代码,该储存功能就是run方法,也就是说,Thread类中run方法是存储 线程要运行的代码,
主线程存储在main方法中
class Demo extend Thread{
public void run(){
for(int i=0;i<60;i++){
System.out.print("demo run---"+i);
}
}
public main static void(String[] args){
Demo d = new Demo();
d.start();//开启线程并执行该线程的run方法
d.run();//仅仅是对象调用方法,而线程创建了,并没有执行
for(int x=0;x<60;x++){
System.out.print("Hello world"+x);
}
}
}
线程状态:
被创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop() 或者run 方法里结束了
static Thread currentThread();获取当前线程对象
getName(); 获取线程名称
Thread.currentThread.getName();
设置线程名称,setName或者构造函数
售票的例子
创建线程的第二种方式,实现Runnable接口
步骤:
1、定义类实现Runnable接口
2、覆盖Runnable接口中的run方法,
将线程要运行的代码存放在该run方法中
3、通过Thread类建立线程对象
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
为什么要将Runnable接口的子类对象传递给Thread类的构造函数
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去指定对象的run方法,就必须要明确该run方法所属的对象。
实现方式和 继承方式的区别是什么
实现方式好处,避免了单继承的局限性,在定义线程时,建立使用实现方式
继承的话,继承了 Thread类,就不能再继承其他的类了,因为java是单继承的
两种方式区别:
继承Thread:线程代码存放在thread子类的run方法中
实现Runnable,线程代码存放在接口的子类的run方法中
class Ticket implements Runnable
{
private int tick=100;
public void run(){
while(true){
if(tick>0)
System.out.print("Thread.currentThread().getName()"+"---sale"+tick--);
}
}
}
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 tick=100;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.print("Thread.currentThread().getName()"+"---sale"+tick--);
}
}
}
}
}
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();
}
}
入参的对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1、必须有两个或者两个以上的线程;
2、必须是多个线程使用同一个锁,
3、必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗了资源,相对降低性能,因为判断锁需要消耗资源,产生了死锁
举一个多线程的例子
是否有安全问题,
如何找问题:
1、明确哪些代码是多线程运行代码
2、明确共享数据,一般成员变量都是
3、明确多线程运行代码中哪些语句是操作共享数据的
class Bank
{
private int sum;
Object obj =new Object();
public void add (int n)
{
synchronized(obj)
{
sum=sum+n;
try(Thread.sleep(10);)catch(Exception e){}
System.out.print("sum="+sum);
}
}
}
class Cus implements Runnable
{
private Bank b= new Bank();
public void run (){
for(intx=0;x<3;x++){
b.add(100);
}
}
}
class BankDemo
{
Cus c= new Cus();
Thread t1= new Thread(c);
Thread t2=new Thread(c);
t1.start();
t2.start();
}
同步有两种表现形式,同步代码块和同步函数把修饰符放到函数上,synchronized
class Bank
{
private int sum;
Object obj =new Object();
public synchronized void add (int n)
{
// synchronized(obj){
sum=sum+n;
try(Thread.sleep(10);)catch(Exception e){}
System.out.print("sum="+sum);
// }
}
}
同步函数用的是哪一个锁呢
函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this
class Ticket implements Runnable
{
private int tick=100;
// Object obj = new Object();
public void run(){
while(true){
// synchronized(obj){
this.show();
//}
}
}
public synchronized void show(){
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.print("Thread.currentThread().getName()"+"---show"+tick--);
}
}
}
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();
}
}
不加上static修饰词,在堆内存是共享数据,加上static修饰词后,是在方法区中是共享数据。
如果同步函数被静态修饰后,使用的锁,不在是this了。因为静态方法中,不可以定义this。
静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class, 该对象的类型是Class。静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class
class Ticket implements Runnable
{
private int tick=100;
// Object obj = new Object();
public void run(){
while(true){
// synchronized(obj){
this.show();
//}
}
}
public static synchronized void show(){
if(tick>0)
{
try{Thread.sleep(10);}catch(Exception e){}
System.out.print("Thread.currentThread().getName()"+"---show"+tick--);
}
}
}
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();
}
}
通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。
当同步函数被static修饰时,这时的同步用的是哪个锁呢?
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class
同步代码块和同步函数的区别?
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些
单例设计模式
饿汉式,使用的时候一般都是用饿汉式,但是面试的时候都是考的懒汉式
class Single{
private static final Single s =new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
懒汉式
class Single
{
private static Single s= null;
private Single(){}
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null)
s= new Single();
}
}
return s;
}
}
用双重判断,提高了懒汉式的效率,
饿汉式和懒汉式有什么不同,
懒汉式的特点是实例的延迟加载,懒汉式延迟加载有没有问题?有,如果多线程访问时,会出现安全问题,可以加同步来解决,可以用同步代码块和同步函数都可以解决,稍微有些低效,用双重判断的方式解决这个低效问题。同步代码块的锁是什么,应为使用的static修饰,所以锁是,该类所属的字节码文件,类名.class Single.class。
死锁,用生活例子,例如使用筷子,你有一跟筷子,我有一跟筷子,你要我的筷子,我要你的筷子,互相争抢,就卡这了,谁也用不了。
同步中嵌套同步,而锁却不同。举例子,例如一个线程拿这A锁想进B里,另一个线程拿着B锁,想进A里面。
class Test implement Runnable
{
private boolean flag;
Test(boolean flag){
this.flag=flag;
}
public void run()
{
if(falg)
{
synchronized(MyLock.locka)
{
System.out.print("if locka");
synchronized(MyLock.lockb)
{
System.out.print("if lockb");
}
}
}else
{
synchronized(MyLock.lockb)
{
System.out.print("else lockb");
synchronized(Mylock.locka)
{
System.out.print("else locka");
}
}
}
}
}
class MyLock
{
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Thread t1 =new Thread(new Test(true));
Thread t2 =new Thread(new Test(false));
t1.start();
t2.start();
}
}