1.什么是多线程?
线程其实就是程序执行的一条路径 一个进程可以包含多个线程
多线程是并发进行的
底层 其实还是单线程(cpu运行效率极其高 )
并发:两个任务同时请求运行 而处理器只能执行一个 不过由于处理的特别快感觉是两个任务同时运行
并行:就是两个任务一起同步运行(需要多核cpu)
2.java程序运行原理
jvm是多线程的 有两个线程: 垃圾回收线程 主函数main是个线程
3.Thread类
继承Thread
重写run 方法
调用start方法
public class Main{
public static void main(String args[]) throws Exception{
MyThread mt = new MyThread();
mt.start();
for(int i=1;i<=1000;i++) {
System.out.println("主线程代码");
}
}
}
class MyThread extends Thread{
public void run()
{
for(int i=1;i<=1000;i++) {
System.out.println("我的线程代码");
}
}
}
4.实现Runnable
实现Runnable接口
重写run方法
因为要启动线程必须通过Thread类的start方法才能使用!
所以必须要创建Thread的对象 把Runable作为target参数传递给Thread
public class Main{
public static void main(String args[]) {
demo de = new demo();
new Thread(de).start(); //1.第一种启动线程的方法 匿名对象
Thread t = new Thread(de); //2.第二种启动线程的方法
t.start();
Runnable run = de; //3.第三种启动线程的方法 父类引用指向子类对象
new Thread(run).start();
for(int i=1;i<=1000;i++) {
System.out.println("主线程代码");
}
}
}
class demo implements Runnable
{
@Override
public void run() {
for(int i=1;i<=1000;i++) {
System.out.println("我的线程代码");
}
}
}
Runnable能够实现多线程的原理 (源码分析)
通过作为Thread类的构造参数 传入底层的init方法 最后给Runnable 类型的target赋值 然后去调用run方法
两者的区别:
1.继承 代码比较简单 不过有父类的话就不用了
2.实现 代码比较复杂 可以多实现
个人而言喜欢使用Runnable接口 扩展性比较强
当然具体情况具体分析
6.用匿名内部类实现多线程
public class Main{
public static void main(String args[]) {
new Thread() {
public void run()
{
for(int i=1;i<=1000;i++) {
System.out.println("线程1111111111代码");
}
}
}.start();
new Thread(new Runnable() {
public void run()
{
for(int i=1;i<=1000;i++) {
System.out.println("线程33333333333代码");
}
}
}).start();
}
}
7.Thread的常用方法
1.给线程设置名字
public class Main{
public static void main(String args[]) {
//1.通过String name的构造来取名字
new Thread("线程1") {
public void run()
{
System.out.println(this.getName()+"...线程1111111111代码");
}
}.start();
new Thread("线程2") {
public void run()
{
System.out.println(this.getName()+"...线程2222222222代码");
}
}.start();
//2.通过Thread类的setName方法
new Thread() {
public void run()
{
this.setName("线程3");
System.out.println(this.getName()+"...线程33333333代码");
}
}.start();
Thread t1 = new Thread() {
public void run()
{
System.out.println(this.getName()+"...线程4444444代码");
}
};
t1.setName("线程4");
t1.start();
}
}
}
2.获取当前线程的引用
public class Main{
public static void main(String args[]) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"...线程111111");
}
}).start();
System.out.println(Thread.currentThread().getName()+"...线程222222");
}
}
3.休眠线程
异常处理只能自己处理
因为父类的run方法没有抛出 所以子类的方法一定不能抛出!!
sleep方法是自行调用处理
wait方法是必须由别人调用才能处理
public class Main{
public static void main(String args[]) {
//倒计时器
new Thread() {
@Override
public void run() {
for(int i=10;i>0;i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("倒计时"+i+"秒");
}
}
}.start();
}
}
4.守护线程
守护线程就是例如象棋中的车马炮
而被守护的线程就是象棋中的帅 如果当帅的线程结束了 那么整个守护线程就立马结束(但是实际情况中是守护线程还是会执行一下 有遗留的数据要处理)
本demo中 t1为非守护线程
public class Main{
public static void main(String args[]) {
Thread t1 = new Thread() {
@Override
public void run() {
for(int i=2;i>0;i--) {
System.out.println("线程1....111");
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for(int i=10;i>0;i--) {
System.out.println("线程2....222");
}
}
};
t2.setDaemon(true);
t1.start();
t2.start();
}
}
5.加入线程
相当于插队 !
就是线程1在运行的时候 线程2插队进来 等线程2结束后 线程1才继续执行
匿名内部类在使用所在方法中的局部变量时该变量必须要使用final修饰!或者事实上为final的
public class Main{
public static void main(String args[]) {
Thread t1 = new Thread() {
@Override
public void run() {
for(int i=10;i>0;i--) {
System.out.println("线程1....1111111111111111");
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for(int i=10;i>0;i--) {
if(i==8) {
try {
t1.join(); //t1进来插队
//t1.join(1); //t1进来插队 1毫秒以后 t1 t2又继续交替运行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("线程2....222");
}
}
};
t1.start();
t2.start();
}
}
6.礼让线程
效果不是很明显 了解即可
7.设置线程的优先级
效果也不是很明显
public class Main{
public static void main(String args[]) {
Thread t1 = new Thread() {
@Override
public void run() {
for(int i=100;i>0;i--) {
System.out.println("线程1....1111111111111111");
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for(int i=100;i>0;i--) {
System.out.println("线程2....22");
}
}
};
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
8.同步代码块
锁要一致
好处是 在每个任务的多条执行命令下不会错乱 线程之间的逻辑不会相互影响 造成乱码或者错误异常
例如本demo中的打印可能会出现 打印不正确的情况 虽然概率低不过存在
为了保证以上错误不会出现 所以要加锁!保证线程安全
public class Demo1_Synchronized {
/**
* @param args
* 同步代码块
*/
public static void main(String[] args) {
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() {
//synchronized(new Demo()) { //同步代码块,锁机制,锁对象可以是任意的
synchronized(d) {
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
}
public void print2() {
//synchronized(new Demo()) { //锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(d) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
class Demo{}
使用静态同步方法
class Printer2 {
Demo d = new Demo();
//非静态的同步方法的锁对象是神马?
//答:非静态的同步方法的锁对象是this
//静态的同步方法的锁对象是什么?
//是该类的字节码对象
public static synchronized void print1() { //同步方法只需要在方法上加synchronized关键字即可
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
public static void print2() {
//synchronized(new Demo()) { //锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(Printer2.class) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
使用非静态同步方法
class Printer2 {
Demo d = new Demo();
//非静态的同步方法的锁对象是神马?
//答:非静态的同步方法的锁对象是this
//静态的同步方法的锁对象是什么?
//是该类的字节码对象
public synchronized void print1() { //同步方法只需要在方法上加synchronized关键字即可
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
public void print2() {
//synchronized(new Demo()) { //锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(this) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
9.线程安全问题
第一种用Thread类继承
public class Demo3_Ticket {
/**
* 需求:铁路售票,一共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; //静态共享
//private static Object obj = new Object(); //如果用引用数据类型成员变量当作锁对象,必须是静态的
public void run() {
while(true) {
synchronized(Ticket.class) {
if(ticket <= 0) {
break;
}
try {
Thread.sleep(10); //线程1睡,线程2睡,线程3睡,线程4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...这是第" + ticket-- + "号票");
}
}
}
}
第二种用Runnable
public class Demo4_Ticket {
/**
* @param args
* 火车站卖票的例子用实现Runnable接口
*/
public static void main(String[] args) {
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;
@Override
public void run() {
while(true) {
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-- + "号票");
}
}
}
}
10.多线程死锁问题
互相不肯释放
所以避免死锁 同步代码块不要嵌套
public class Demo5_DeadLock {
/**
* @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();
}
}
Collections 可以调用方法始集合都可以变成线程安全
所以说Vector和Hashtable都被淘汰了