多线程的基本用法和问题

 

 

1.多线程的引入

什么是线程
	* 线程是程序执行的一条路径, 一个进程中可以包含多条线程
	* 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
(假如我们电脑同时做着几件事:qq,听歌,下载。
但cpu不是同时执行的,他是在这三个任务里面分别执行一段,来回切换,
由于他的速度很快.表面上看起来是同时执行的。
当我们打开很多个任务时,我们就会发现cpu的风扇很快,噪音很大,
因为cpu需要不停的分配时间,
因此说明:我们多线程的底层都是单线程,只不过多线程是利用了cpu的空余时间)

 

2.多线程并行和并发的区别

 

* 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,
由于时间间隔较短,使人感觉两个任务都在运行。(就是1所述)

 

3.Java程序运行原理和JVM的启动是多线程的吗

 

A:Java程序运行原理
	* Java命令会启动java虚拟机,启动JVM,
	* 等于启动了一个应用程序,也就是启动了一个进程。
	* 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
B:JVM的启动是多线程的吗
	* JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
public class a {

	/**
	 * @param args
	 * 证明jvm是多线程的
	 */
	public static void main(String[] args) {
		for(int i = 0; i < 100000; i++) {
			new Demo();
		}
		
		for(int i = 0; i < 10000; i++) {
			System.out.println("我是主线程的执行代码");
		}
	}

}

class Demo {

	@Override
	public void finalize() {
		System.out.println("垃圾被清扫了");
	}
	
}

4、两种多线程的实现方式

4.1、多线程程序实现的方式1

1.继承Thread
	* 定义类继承Thread
	* 重写run方法
	* 把新线程要做的事写在run方法中
	* 创建线程对象
	* 开启新线程, 内部会自动执行run方法

 

 

 

public class b {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyThread mt = new MyThread();		//4,创建Thread类的子类对象
		//mt.run();//先执行run方法并没有开启线程,他就是一个普通方法,先输出a后输出b.
		mt.start();				//5开启线程,先b后a交替出现,开启线程也需要时间
		for(int i = 0; i < 1000; i++) {
			System.out.println("bbbbbbbbbbb");
		}
	}

}
class MyThread extends Thread {				//1,继承Thread
	public void run() {				//2,重写run方法
		for(int i = 0; i < 1000; i++) {		//3,将要执行的代码写在run方法中
			System.out.println("aaa");
		}
	}
}

 

 

 

 

 

 

多线程程序实现的方式2

2.实现Runnable
	* 定义类实现Runnable接口
	* 实现run方法
	* 把新线程要做的事写在run方法中
	* 创建自定义的Runnable的子类对象
	* 创建Thread对象, 传入Runnable
	* 调用start()开启新线程, 内部会自动调用Runnable的run()方法

 

 

 

接口 Runnable

已知实现类: 
 Thread, TimerTask 
public interface RunnableRunnable
 接口应该由那些打算通过某一线程执行其实例的类来实现。
 类必须定义一个称为 run 的无参数方法。 

 

void run()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。 
方法 run 的常规协定是,它可能执行任何所需的动作。 

 

 

 

 

public class c  {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();	//4,创建Runnable的子类对象
		//Runnable target = mr;	mr = 0x0011
		Thread t = new Thread(mr);		//5,将其当作参数传递给Thread的构造函数
		t.start();				//6,开启线程
		
		for(int i = 0; i < 1000; i++) {
			System.out.println("bb");
		}
	}

}

class MyRunnable implements Runnable {		//1,定义一个类实现Runnable

	@Override
	public void run() {				//2,重写run方法
		for(int i = 0; i < 1000; i++) {		//3,将要执行的代码写在run方法中
			System.out.println("aaaaaaaaaaaa");
		}
	}
	
}

 

 

 

 

 

实现Runnable的原理
查看源码
	* 1,看Thread类的构造函数,传递了Runnable接口的引用 
	* 2,通过init()方法找到传递的target给成员变量的target赋值
	* 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

 

多线程两种方式的区别
* 查看源码的区别:
	* a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
	* b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, 
  start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 
    不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
* 继承Thread
	* 好处是:可以直接使用Thread类中的方法,代码简单
	* 弊端是:如果已经有了父类,就不能用这种方法
* 实现Runnable接口
	* 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
	* 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

 

 

 

类 Thread

java.lang.Object
——java.lang.Thread
所有已实现的接口:Runnable 
public class Thread extends Object implements Runnable
线程 是程序中的执行线程。
Java 虚拟机允许应用程序并发地运行多个执行线程。 
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
每个线程都可以或不可以标记为一个守护程序。
当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,
并且当且仅当创建线程是守护线程时,新线程才是守护程序。 
每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。 

 

public Thread()
分配新的 Thread 对象。
这种构造方法与 Thread(null, null, gname) 具有相同的作用,
其中 gname 是一个新生成的名称。
自动生成的名称的形式为 "Thread-"+n,其中的 n 为整数。 

 

 

public Thread(String name)
分配新的 Thread 对象。这种构造方法与 Thread(null, null, name) 具有相同的作用。 
参数:
name - 新线程的名称。

 

 

public Thread(Runnable target)
分配新的 Thread 对象
这种构造方法与 Thread(null, target,gname) 具有相同的作用

 

 

public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。 
*多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。 
抛出: 
IllegalThreadStateException - 如果线程已经启动。

 

 

 

 

 

 

多线程匿名内部类实现线程的两种方式

 

public class d {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//继承Thread类
		new Thread() {					//1,new 类(){}继承这个类
			public void run() {			//2,重写run方法
				for(int i = 0; i < 3000; i++) {	//3,将要执行的代码,写在run方法中
					System.out.println("aaaaaa");
				}
			}
		}.start();					//4,开启线程
		
		
		//实现Runnable接口
		new Thread(new Runnable(){		       //1,new类(new接口()){}实现这个接口
			@Override
			public void run() {			//2,重写run方法
				for(int i = 0; i < 3000; i++) {	//3,将要执行的代码,写在run方法中
					System.out.println("bb");
				}
			}				
		}).start(); 					//4,开启线程
	}

}

 

 

 

 

 

多线程获取名字和设置名字

 

public class e {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread() {		//默认名字Thread-0, ...1 2 3....					
			public void run() {
				System.out.println(this.getName() + "....aa");
			}
		}.start();
		
		new Thread("hello") {//通过构造方法给name赋值
			public void run() {
				System.out.println(this.getName() + "....bb");
				this.setName("java");
				System.out.println(this.getName() + "....cc");
			}
		}.start();
		
		
		
		Thread t1 = new Thread() {
			public void run() {
				System.out.println(this.getName() + "....ee");
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				this.setName("李四");
				System.out.println(this.getName() + "....ff");
			}
		};
		
		t1.setName("张三");
		t1.start();
		t2.start();
	}

}

 

 

 

 

 

public static Thread currentThread()
	返回对当前正在执行的线程对象的引用。 

 

 

public class f {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread() {
			public void run() {
				System.out.println(getName() + "....aaa");
			}
		}.start();
		
		new Thread(new Runnable() {
			public void run() {
				//Thread.currentThread()获取当前正在执行的线程
				System.out.println(Thread.currentThread().getName() + "...bbb");
			}
		}).start();
		
		获取主函数线程的引用,并获取名字
		System.out.println(Thread.currentThread().getName());//main
		Thread.currentThread().setName("我是主线程");
		System.out.println(Thread.currentThread().getName());//没有顺序执行
	}

}

 

 

 

休眠线程

 

public static void sleep(long millis)throws InterruptedException
	在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),

public static void sleep(long millis,int nanos)throws InterruptedException
	在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),

 

 控制当前线程休眠若干毫秒
* 1秒= 1000毫秒
*  1秒 = 1000 * 1000 * 1000纳秒 1000000000

 

 

public class g {

	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {
		
		/*for(int i = 20; i >= 0; i--) {
			Thread.sleep(1000);
			System.out.println("倒计时第" +i + "秒");
		}*/
		
		//异常catch处理
		new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(getName() + "...aaaaaa");
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
					System.out.println(getName() + "...bbbbbb");
				}
			}
		}.start();
	}

}

 

 

 

 

守护线程

public final void setDaemon(boolean on)
	将该线程标记为守护线程或用户线程。
	当正在运行的线程都是守护线程时,Java 虚拟机退出。 
	该方法必须在启动线程前调用。  
	(设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出)
	参数:
	on - 如果为 true,则将该线程标记为守护线程。 

 

 

public class h {

	/**
	 * @param args
	 * 守护线程
	 */
	public static void main(String[] args) {
		Thread t1 = new Thread() {
			public void run() {
				for(int i = 0; i < 2; i++) {
					System.out.println(getName() + "...aaaaaaaa");
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 50; i++) {
					System.out.println(getName() + "...bb");
				}
			}
		};
		
		t2.setDaemon(true);	
		//设置为守护线程,还有个缓冲时间
		
		t1.start();
		t2.start();

	}

}

 

 

 

 

 

加入线程

public final void join() throws InterruptedException
等待该线程终止。 

public final void join(long millis)throws InterruptedException
  等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。     

 

 

public class i {

	/**
	 * @param args
	 * join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
	 * join(int), 可以等待指定的毫秒之后继续
	 * 匿名内部类在使用他方法中的局部变量时,这个局部变量必须用final修饰
	 */
	public static void main(String[] args) {
		final Thread t1 = new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(getName() + "...aaaa");
				}
			}
		};
		
		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					if(i == 2) {
						try {
							t1.join();//插队,加入
							//t1.join(1);//插队指定的时间,过了指定时间后,两条线程交替执行
						} catch (InterruptedException e) {
							
							e.printStackTrace();
						}
					}
					System.out.println(getName() + "...bb");
				}
			}
		};
		
		t1.start();
		t2.start();

	}

}

 

 

 

 

 

礼让线程

public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。   

 

 

public class j {

	/**
	 * yield让出cpu礼让线程
	 */
	public static void main(String[] args) {
		new My_Thread().start();
		new My_Thread().start();
	}

}

class My_Thread extends Thread {
	public void run() {
		for(int i = 1; i <= 1000; i++) {
			if(i % 10 == 0) {
				Thread.yield();	//让出CPU
				System.out.println(getName() + "让出cpu" );
			}
			System.out.println(getName() + "..." + i);
		}
	}
}

 

 

 

 

 

优先级

 public final void setPriority(int newPriority)
 更改线程的优先级。 
 默认为5,最小为1,最大为10

 

 

public class k {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Thread t1 = new Thread(){
			public void run() {
				for(int i = 0; i < 100; i++) {
					System.out.println(getName() + "...aaa" );
				}
			}
		};
		
		Thread t2 = new Thread(){
			public void run() {
				for(int i = 0; i < 100; i++) {
					System.out.println(getName() + "...bb" );
				}
			}
		};
		
		/*t1.setPriority(10);		//			设置最大优先级
		t1.setPriority(1);*/
		t1.setPriority(Thread.MAX_PRIORITY);		//设置最小的线程优先级
		t2.setPriority(Thread.MIN_PRIORITY);		//设置最大的线程优先级
		t1.start();
		t2.start();
	}

}

 

 

 

 

 

 

 

多线程同步代码块

* 1.什么情况下需要同步
	* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
	* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
* 2.同步代码块
	* 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
	* 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

 

 

 

public class L {

	/**
	 * @param args
	 * 同步代码块
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final Printer p = new Printer();
		/**
		 * 静态只能调用静态,我们是创建了外部类对象,然后在调用他的方法.所以不用静态
		 * 但如果是内部类,必须静态类...
		 * */
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();	
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
		
	}
}

class Printer {
	
	Demo d = new Demo();
	public void print1(){
		//System.out.println("a");
		synchronized(d){		//同步代码块,锁机制,锁对象可以是任意的
			锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
			//因为匿名对象不是同一个对象
			System.out.print("你");
			System.out.print("好");
			System.out.print("世");
			System.out.print("界");
			System.out.print("\r\n");
			}
		}
	public void print2(){
		//System.out.println("b");
		synchronized(d){
			System.out.print("我");
			System.out.print("爱");
			System.out.print("吃");
			System.out.print("棒");
			System.out.print("棒");
			System.out.print("糖");
			System.out.print("\r\n");
		}
		
	}
}

class Demo{}//任意对象

 

 

 

 

 

3.使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

 

 

public class L {

	/**
	 * @param args
	 * 同步代码块
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final Printer p = new Printer();
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();	
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
		
	}
}

class Printer {
	
	//非静态的同步方法的锁对象是什么?
	//答:非静态的同步方法的锁对象是this
	//静态的同步方法的锁对象是什么?
	//是该类的字节码对象
	public synchronized void print1(){	//同步方法只需要在方法上加synchronized关键字即可
		锁对象不能用匿名对象,因为匿名对象不是同一个对象
		System.out.print("你");
		System.out.print("好");
		System.out.print("世");
		System.out.print("界");
		System.out.print("\r\n");
	
		}
	public void print2(){
		synchronized(this){
			System.out.print("我");
			System.out.print("爱");
			System.out.print("吃");
			System.out.print("棒");
			System.out.print("棒");
			System.out.print("糖");
			System.out.print("\r\n");
		}
	}
}

class Demo{}//任意对象

 

 

 

 

 

 

多线程安全问题

* 多线程并发操作同一数据时, 就有可能出现线程安全问题
* 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
* 
 需求:铁路售票,一共100张,通过四个窗口卖完.

 

 

 

public class m {

	/**
	 * 需求:铁路售票,一共100张,通过四个窗口卖完.
	 * 通过四个线程出售
	 */
	public static void main(String[] args) {
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
		new Ticket().start();
	}

}

class Ticket extends Thread {
	private static int ticket = 100;//加static,所有对象共享这100张票
	//private Object obj = new Object();//相当于成员变量,前加static才能做锁对象	
	//private static Object obj = new Object();	//如果用引用数据类型成员变量当作锁对象,必须是静态的
	public void run() {
		while(true) {
			synchronized(Ticket.class) {//加this不行,每new一次都是一个新的对象新的锁
				if(ticket <= 0) {
					break;
				}
				
				try {
					Thread.sleep(10);	//线程1睡,线程2睡,线程3睡,线程4睡
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(getName() + "...这是第" + ticket-- + "号票");
			}
		}
	}
}

 

 

 

 

 

 

public class n {

	/**
	 * @param args
	 * 火车站卖票的例子用实现Runnable接口
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyTicket mt = new MyTicket();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
		
		
		/*Thread t1 = new Thread(mt);	//多次启动一个线程是非法的
		t1.start();
		t1.start();
		t1.start();
		t1.start();*/
	}

}

class MyTicket implements Runnable {
	private int tickets = 100;//不用static,他不用创建多个对象,只需当做参数传递
	@Override
	public void run() {
		while(true) {
			//synchronized(Ticket.class),同理,锁对象也可以用this
			synchronized(this) {
				if(tickets <= 0) {
					break;
				}
				try {
					Thread.sleep(10);	//线程1睡,线程2睡,线程3睡,线程4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");
			}
		}
	}
}

 

 

 

 

 

 

public class o {

	/**
	 * @param args
	 * 需求:铁路售票,一共100张,通过四个窗口卖完.
	 */
	public static void main(String[] args) {
		TicketsSeller t1 = new TicketsSeller();
		TicketsSeller t2 = new TicketsSeller();
		TicketsSeller t3 = new TicketsSeller();
		TicketsSeller t4 = new TicketsSeller();
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		t4.setName("窗口4");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}

}

class TicketsSeller extends Thread {
	private static int tickets = 100;
	static Object obj = new Object();
	public TicketsSeller() {
		super();
		
	}
	public TicketsSeller(String name) {
		super(name);
	}
	public void run() {
		while(true) {
			synchronized(obj) {
				if(tickets <= 0) 
					break;
				try {
					Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				System.out.println(getName() + "...这是第" + tickets-- + "号票");
			}
		}
	}
}

 

 

 

 

 

多线程死锁

 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
	* 尽量不要嵌套使用

 

 

 

public class p {

	/**
	 * @param args
	 */
	private static String s1 = "筷子左";
	private static String s2 = "筷子右";

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s1) {
						System.out.println(getName() + "...获取" + s1 + "等待" + s2);
						synchronized(s2) {
							System.out.println(getName() + "...拿到" + s2 + "开吃");
						}
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					synchronized(s2) {
						System.out.println(getName() + "...获取" + s2 + "等待" + s1);
						synchronized(s1) {
							System.out.println(getName() + "...拿到" + s1 + "开吃");
						}
					}
				}
			}
		}.start();
	}
}

 

 

 

 

 

 

 

 

回顾以前说过的线程安全问题(面试常考)
	* 看源码:Vector,StringBuffer,Hashtable,
	* Collections.synchroinzed(xxx)方法,同步集合使其线程安全
	* Vector是线程安全的,ArrayList是线程不安全的
	* StringBuffer是线程安全的,StringBuilder是线程不安全的
	* Hashtable是线程安全的,HashMap是线程不安全的


 

 

 

 

 

 

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值