java多线程

一、线程、进程的概念

进程:进程就是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

线程:线程就是进程中的一个独立的控制单元,线程在控制着进程的执行。

注意:
1、一个进程中至少有一个线程!
2、java vm启动的时候会有一个进程java.exe。
3、该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。
4、jvm启动不止一个线程,还有负责垃圾回收机制的线程。所以应该有很多的线程的!

线程默认名称格式:Thread-编号(编号是从0开始计算的),方法static Thread.currentThread()可以获取当前线程的对象。

二、创建线程
1、继承Thread(线程代码存放在Thread子类run方法中)
2、实现Runnable方法(线程代码存放在接口的子类的run方法中。一般使用该方法!)

通过Runnable接口创建线程的方法:
1、首先必须是“执行”Runnable接口的,也就是类实现Runnable接口。
2、覆盖Runnable接口中的run()方法,将线程要运行的代码存放在run方法中。
3、通过Thread类及建立线程对象。
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法。

注意:但是为什么要将Runnable接口的子类对象传递给Thread的构造函数呢?这是因为自定义的run方法所属的对象是Runnable接口的子类对象所以要让线程去指定对象的run方法。

继承Thread创建线程:

/*
 * 创建两个线程,和主线程交替运行
 * 
 * */

//需要注意的是:一个类可以创建多个对象!
class DemoDemo extends Thread{//创建的第一个线程
	DemoDemo(String name){
		setName(name);
	}
	public void run(){//必须调用run方法的,因为run方法是Tread类中德方法,必须进行覆盖重写才可以,所以这是一个已经存在的方法了!
		for(int i=0;i<=100;i++){
			System.out.println("我是第1个线程"+i+".."+"名字:"+this.getName());
		}
	}
}
class DemoDemo01 extends Thread{//创建的第二个线程
	DemoDemo01(String name){
		setName(name);
	}
	public void run(){
		for(int i=0;i<=100;i++){
			System.out.println("我是第2个线程"+i+".."+"名字:"+Thread.currentThread().getName());//输出的效果是一样的!相当于:this==Thread.currentThread()
		}
	}
}
class NameDemo extends Thread{//第三个名称输出类,作用是将上面的两个线程类中的内容进行调用即可
	public void run(){
		DemoDemo d1=new DemoDemo("majiheng");
		DemoDemo01 d2=new DemoDemo01("chenyici");	
		d1.start();
		d2.start();
		String name1=d1.getName();
		String name2=d2.getName();
		System.out.println("第一个线程的名字:"+name1+"第二个线程的名字:"+name2);
		}
}

//主函数
public class ThreadDemoDemo {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
//		Thread t=new Thread();
//		DemoDemo d1=new DemoDemo();
//		DemoDemo01 d2=new DemoDemo01();				//我们这里如果使用的是:di.run()方法的话,将会出现的是固定的内容!这也是一个长考的面试题!
//		d1.start();
//		d2.start();
//		String name1=d1.getName();
//		String name2=d2.getName();
//		System.out.println("第一个线程的名字:"+name1+"第二个线程的名字:"+name2);
//		for(int i=0;i<=100;i++){		//主函数的线程
//			System.out.println("我是主线程!"+i);
//		}
		
		NameDemo n=new NameDemo();//创建线程的对象,后面进行方法的调用
		n.start();
		String name=n.currentThread().toString();
		System.out.println("名字:"+name);
	}

}

执行Runnable创建线程:

/*
 * 这个使用的是Runable接口实现的线程
 * */

class AA implements Runnable{
	String name1,name2;
	AA(String name1,String name2){
		this.name1=name1;
		this.name2=name2;
	}
	@Override
	public void run() {//会提示要有run方法,因为前面已经定义了Runnable
		// TODO Auto-generated method stub
		int i=0;
		while(true){
			if(Thread.currentThread().getName().equals("majiheng")){
				i++;
				System.out.println(name1+"线程中的局部变量是:"+i);
				return;//结束执行
			}
			else if(Thread.currentThread().getName().equals("chenyici")){
				i--;
				System.out.println(name2+"线程中的局部变量是:"+i);
				return;//结束执行
			}
		}
	}
	}
public class ThreadsDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		AA a=new AA("majiheng","chenyici");
		Thread t=new Thread(a);
		t.start();
	}

}

 

5、调用Thread类的start方法开启线程并且调用Runnable接口子类的run方法

run方法:
run()方法就是存储线程代码的一个方法!是要是线程被执行的时候,一定会被执行到的!

设置和获取线程名字:
1、使用setName()方法设置名字。
2、使用构造方法设置线程名字。
3、使用getName()方法获取线程名字。
4、使用Thread.CurrentThread.toString()可以直接获取当前线程的名字以及优先级别。
5、使用join()方法可以将一个线程添加到另一个线程中(当A线程执行到了B线程的join方法的时候,A线程就会等待,等B线程执行完之后,A才会继续执行的,里面封装了唤醒机制)。

this关键字:
在同步代码块中,参数是一个类创建的对象,所以如果没有特别说明,自己可以随便创建一个对象添加到里面,但是这样是不安全的!同步函数中的锁是“this”,所以为了使程序的安全性更好,建议使用this关键字,也就是同步函数的this锁,这是非常重要的!

三、同步代码块
安全问题:当多条语句在操作同一共享数据的时候,当一个线程正在对此数据进行操作的时候,还没有执行完,另一个线程参与进来,导致共享数据被破坏,产生了错误,这也是相当常见的一种情况。使用同步代码块,对多条操作共享数据的语句,只能让一个线程都执行完之后才可以继续,在执行的过程中,其他的线程不可以参与执行!

同步的前提:
1、首先,必须要有两个或者两个以上的线程。
2、必须使用的时共享数据,多个线程同时使用一个数据。

同步“利”与“弊”:
1、“利”是解决了多线程的安全问题。
2、“弊”是每次都进行判断,这是多么的浪费资源,有木有?!

“同步代码”块具体格式:
synchronized(需要同步的对象){
 需要被同步的代码块[也就是说这里的代码块有可能出现错误]!
}
注意:同步的对象一般使用Object。

代码示例:

/*
 * 生产者和消费者问题,使用的方法是synchronized同步函数,没有进行升级操作,是一般的消费者与生产者
 * 
 * 
 * 问题中使用implements Runnable来进行线程的创建
 * */

class Sum{	//中转站
	private String name;
	private int conter=0;
	private boolean point=false;
	
	public synchronized void set(String name){//输入函数
		if(point)	//如果是假的,近忽略try...catch
			try{
				wait();
			}
			catch(Exception e){
			}
			this.name=name+"......."+conter++;
			System.out.println(Thread.currentThread().getName()+"...输入者..."+this.name);//线程的名字
			point=true;
			this.notifyAll();//唤醒所有
	}
	public synchronized void out(){	//同步输出函数
		if(!point)
			try{
				wait();
			}
			catch(Exception e){				
			}
			//输出里面没有name,不定义
			System.out.println(Thread.currentThread().getName()+"---输出者---"+this.name);
			point=false;
			this.notifyAll();
	}
}
class Producer implements Runnable{//输入者		里面一般有自己的构造方法 和 复写父类的run方法
	private Sum s;
	Producer(Sum s){
		this.s=s;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){		//while循环
			s.set("+商品+");
		}
	}
	
}

class Consumer implements Runnable{//输出者		和上面一样,一般使用的是自己的构造方法和复写父类的run方法
	private Sum s;
	Consumer(Sum s){
		this.s=s;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			s.out();
		}
	}
	
}
public class ProducerAndCustomer {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Sum s=new Sum();
		
		Producer p=new Producer(s);
		Consumer c=new Consumer(s);
		
		Thread t1=new Thread(p);
		Thread t2=new Thread(c);

		t1.start();
		t2.start();
		
		
	}

}

 

锁(synchronized):
“上锁”是为了解决安全问题,但是如果上了“锁”还是没有解决多线程的安全问题的话,该怎样呢?
1、先确定是不是多线程,是不是每个一线程都被“上锁”了?
2、是不是用的同一把锁?锁唯一吗?
注意:只要判断了这两个方面就可以了,问题就会解决!当然第2中判断中一般使用的是公有的“对象”,就是说主函数中创建的共同使用的对象。

死锁:
死锁就是我里面有你的锁,你里面有我的锁,然后一运行就程序卡死(面试的时候可能会出“死锁”的相关程序题)。

锁总结:
1、对于同步函数:参数中的使用的是this关键字,也就是说this是同步函数的“锁”。
2、对于静态的同步函数:静态同步函数使用的“锁”是该方法所在类的字节码文件对象,也就是说:类名.class。
3、静态进内存的时候,内存中还没有本类的对象创建,但是一定有该类对应的字节码文件对象:类名.class,所以该对象的类型是Class!

锁的进化:
1、jdk1.5以后,synchronized被Lock替代了,Object、wait、notify、notifyAll被Condition替代了,虽然感觉比较坑人,但是功能增强了!

等待唤醒机制:
1、常用方法:wait()、notify()、notifyAll()
2、等待唤醒的方法,都是使用在同步中,原因是要对持有监视器(锁)的线程进行操作,所以要使用在同步中,因为同步中才有锁。
3、为什么这些操作线程的方法要定义Object类中?因为这些方法在操作同步中线程的时候,都必须要标识他们所操作的线程只有锁,只有同一个锁上的被等待的线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说:等待和唤醒必须是同一个锁!而锁是可以用任意的对象,所以可以被任意的对象调用的方法定义Object类中。

线程的停止:
1、以前使用stop方法,但是已经过时,所以不使用。
2、使run方法停止运行,只要控制住循环,就可以让run方法结束,也就是线程的结束。
3、特殊情况下:就不会读取到标记,那么线程就不会结束,当没有指定的方式让冻结的线程恢复到运行状态的时候,这个时候就需要对冻结的进行清除,强制让线程恢复到运行状态中,这样就可以操作标记让线程结束。

懒汉&饿汉:
1、面试题:懒汉与饿汉有什么不同?
答:懒汉是实例的延迟加载。
2、面试题:懒汉的延迟加载有没有问题?怎么解决?加同步的时候,使用的是哪个锁?
答:有,如果多线程访问的时候会出现安全问题,可以加同步来解决,用“同步代码块”或者“同步函数”都是可以的,但是有点低效,所以用双重否定可以解决这个低效的问题,使用的是“该类所属的字节码文件对象”。

饿汉&懒汉简介:

1、饿汉式:(上来就开辟内存,也可以理解成上来就“吃内存”,和前一个总结一样,分为三个步骤)

private Single(){} //私有化构造函数,防止别人创建过多对象
private static Single s=new Single(); //在本类中创建对象,用于下面的返回中
public static Single getInstance(){ //创建一个方法,用来返回对象创建的s
 return s;
}


2、懒汉式:(可以理解成“吃饱喝足了”,别人再“端上来东西”的时候,“懒汉“要判断一下,自己吃不吃,也就是判断一下”需不需要占用内存“)

private Single(){}
private static Single s=null; //前面两步骤都一样,主要是后面的判断
public static Single getInstance(){
 if(s==null){ //先判断一下后面的对象是不是为空,空则进行,不空则退出。
  synchronized(Single.class){ //synchronized的作用是”上锁“,防止过多的创建对象,后面“同步函数“中会讲到
   if(s==null)  //如果为空,就创建一个新的对象,如果不为空,就退出。
    s=new Single();
  }
 }
 return s;
}

3、类的“唯一性”:

就是说在“堆”内存中,只允许一个类创建的一个对象的存在,然后在主函数(主方法)中创建N多对象对同一个“堆”内存中的对象进行引用。

如果要求类的唯一性,我们就采用“单例设计模式”即可对这个类进行“唯一性”操作,一共需要“三步”即可搞定:

第一步:为了防止别人创建对象使用“构造函数”,将构造函数private私有化。
第二步:在类中创建一个本类的对象,一般都“私有化”以及“静态”。
第三步:提供一个可以获得该对象的方法,用于返回前面“本类”中创建的对象的名字。

什么情况下使用以上步骤?

1、对事物的描述还是那个样子,该怎样就怎样。
2、当需要对该事物的对象保证在内存中唯一的时候,就使用以上的三个步骤即可。

类的“唯一性”示例代码:

class ChenYiCi{
	private static String name;//因为下面使用该变量的方法是“静态”的,所以这里的变量都是“静态”修饰的
	private static int age;
	
	private ChenYiCi(){}//构造函数私有化,防止别人创建对象调用构造方法,所以将“构造函数”干掉!
	private static ChenYiCi cyc=new ChenYiCi();//在“本类”中创建一个对象,目的是让下面的方法进行返回
	public static ChenYiCi f1(){
		return cyc;//返回之后定位到“本类”然后就可以执行类中的其他方法了
	}
	
	public void setName(String name){
		this.name=name;
	}
	public String getName(){
		return name;
	}
	public static void setAge(int age){
		age=age;
	}
	public int getAge(){
		return age;
	}
	
	public static void f(){//修饰成“静态”的可以使用“类名”调用方法
		System.out.println("name="+name+"age="+age);
	}
	
}
public class DuiXiangDeWeiYiXing {

	/**
	 * 为了使“陈一词”的对象唯一性,方便所有的人都使用“陈一词”这个对象
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ChenYiCi c1=ChenYiCi.f1();//类名只能调用“静态”修饰的方法
		ChenYiCi c2=ChenYiCi.f1();
		/*
		 * 这样就可以实现多个“事物”调用“同一个事物”
		 * */
		
	}

}

 

守护进程:
1、概念:“守护进程”也称为“后台线程”,也就是在后台默默无闻运行的线程!“用户线程”就是前台线程,“守护线程”就是后台线程,“后台线程”依赖于“前台线程”,也就是说,如果“前台线程”消失的话,那么“后台线程”也就完蛋了!这是一个“依赖“和”被依赖“的关系。

示例代码:

/*
 * 写一个守护线程,
 * 先写一个用户线程,然后写一个守护线程,然后让这两个线程一起结束,看看守护线程是什么反应
 * 
 * */

class ShouHuXianCheng implements Runnable{//为了让两个线程都在一个类中,必须在这个类中创建两个线程,一个用户,一个守护
	Thread t1,t2;
	ShouHuXianCheng(){
		t1=new Thread(this);//在方法ShouHuXianCheng中创建线程对象t1和t2
		t2=new Thread(this); 
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if(Thread.currentThread()==t1){
			for(int i=0;i<5;i++){
			System.out.println("我是用户进程"+i);//线程一
			try{		//try...catch
				Thread.sleep(1000);
			}
			catch(Exception e){
				
			}
			}
		}
		/*
		 * 如果用户线程结束,并且守护线程由于多余用户线程的执行次数但是也结束了,那么就证明守护线程依赖于用户线程,
		 * 所以自己这里定义用户进程的时候,用的是for循环5次,但是守护进程用的是for循环100次,如果后面运行的时候有出现
		 * 用户进程结束,守护进程也跟着结束的话,那么说明了守护进程依赖于用户进程了。
		 * */
		else if(Thread.currentThread()==t2){
			for(int i=0;i<=100;i++){
				System.out.println("我是守护进程"+i);//
				try{
					Thread.sleep(1000);
				}
				catch(Exception e){
					
				}
			}
		}
	}
	
}
public class ShouHuXianChengDemo {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ShouHuXianCheng s=new ShouHuXianCheng();
//		s.t1.setDaemon(true);
		s.t1.start();//启动线程
		s.t2.setDaemon(true);//命名为守护线程
		s.t2.start();//启动线程2
	}

}


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值