一.线程的概念
1.什么是线程
线程是程序执行的一条路径,一个进程中可以包含多条线程
多线程并发执行可以提高程序的效率, 可以同时完成多项工作
2.多线程的应用场景
红蜘蛛同时共享屏幕给多个电脑
迅雷开启多条线程一起下载
QQ同时和多个人一起视频
服务器同时处理多个客户端请求
二.开启新线程
1.继承Thread
定义类继承Thread
重写run方法
把新线程要做的事写在run方法中
创建线程对象
开启新线程, 内部会自动执行run方法
public class Demo1_Thread {
/**
* @param args
* 第一种继承Thrad类
*/
public static void main(String[] args) {
MyThread mt = new MyThread(); //4,创建自定义类对象
mt.start(); //5,开启线程
for(int i = 0; i < 10000; i++) {
System.out.println("bb");
}
//System.out.println(1/0);
}
}
class MyThread extends Thread { //1,自定义类继承Thread
public void run() { //2,重写run方法
for(int i = 0; i < 10000; i++) { //3,将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaaaaaaaa");
}
}
}
2.实现Runnable
定义类实现Runnable接口
实现run方法
把新线程要做的事写在run方法中
创建自定义的Runnable对象
创建Thread对象, 传入Runnable
调用start()开启新线程, 内部会自动调用Runnable的run()方法
public class Demo3_Runnable {
/**
* @param args
*/
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //4,创建自定义类对象
//Runnalbe target = new MyRunnable();
//mr = 0x0011
Thread t = new Thread(mr); //5,将自定义类对象当作参数传递给Thread的构造函数
t.start(); //6,开启线程
for(int i = 0; i < 10000; i++) {
System.out.println("bb");
}
}
}
class MyRunnable implements Runnable { //1,自定义类实现Runnable接口
@Override
public void run() { //2,重写run方法
for(int i = 0; i < 10000; i++) { //3,将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaaaaaaaa");
}
}
}
(匿名的两种多线程)
public class Demo4_NiMingThread {
/**
* @param args
*/
public static void main(String[] args) {
new Thread() { //new 类(){}这么写的意思是继承这个类
public void run() { //重写run方法
for(int i = 0; i < 10000; i++) { //将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaaaaaaaa");
}
}
}.start(); //开启线程
new Thread(new Runnable() { //new 接口(){}这么写是实现这个接口
public void run() {
for(int i = 0; i < 10000; i++) {
System.out.println("bb");
}
}
}).start();
}
}
3.两种方式的区别
区别一:
a.由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
b.构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量 Runnable是否为空, 不为空则执行Runnable的run()
区别二:
a.继承Thread只能是单继承,如果自己定义的线程类已经有了父类,就不能再继承了
b.实现Runnable接口可以多实现,即使自己定义线程类已经有父类可以实现Runnable接口
总结:继承Thread的好处是:可以直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口的好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也 可以实现接口,而且接口是可以多实现的
弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代 码复杂
三.Thread类常用方法
1.获取名字
通过getName()方法获取线程对象的名字
2.设置名字
通过构造函数可以传入String类型的名字
通过setName(String)方法可以设置线程对象的名字
3.获取当前线程对象
Thread.currentThread(),主线程也可以获取
4.休眠
Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒=1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
5.守护
setDaemon(),设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
6.加入
join(),当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int),可以等待指定的毫秒之后继续
public class Demo5_Name {
/**
* @param args
*/
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(getName() +...aaaaaaaaaaaa");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
System.out.println(getName()+ ...ddddddddddddddddd");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//t1.setDaemon(true); //设置t1为守护线程
t1.setName("one");
t2.setName("two");
System.out.println(t1.getId());
System.out.println(t2.getId());
t1.start();
t2.start();
Thread.currentThread().setName("我是主线程"); //修改主线程的名字
System.out.println(Thread.currentThread().getName()); //获取主线程的名字
}
}
}
四.线程之间的同步
1.什么情况下需要同步
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
2.同步代码块
使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
3.同步方法
使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
非静态同步方法默认使用当前对象this作为锁对象
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 {
//private Demo d = new Demo();
private Object obj = new Object();
public static void print1() {
synchronized(Printer.class){//锁对象可以是任意对象,但是不能是匿名对象,因为不能保证是同一把锁
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
public static synchronized void print2() {//非静态同步函数的锁是this
//synchronized(obj){ //静态方同步函数的锁是该类的字节码对象
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
//}
}
}
4.线程安全问题
多线程并发操作同一数据时,就有可能出现线程安全问题
使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
5.死锁问题
多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
尽量不要嵌套使用
public class Demo4_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) { //获取s1锁
System.out.println(getName() + "拿到" + s1 + "等待" + s2);
synchronized(s2) { //获取s2锁
System.out.println(getName() + "拿到" + s2 + "开吃");
} //释放s2锁
} //释放s1锁
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) { //获取s2锁
System.out.println(getName() + "拿到" + s2 + "等待" + s1);
synchronized(s1) { //获取s1锁
System.out.println(getName() + "拿到" + s1 + "开吃");
} //释放s1锁
} //释放s2锁
}
}
}.start();
}
}
五.线程的方法
1.yield让出cpu
2.setPriority()设置线程的优先级
public class Demo1_Yield {
/**
* @param args
*/
public static void main(String[] args) {
Thread t1=new MyThread();
Thread t2=new MyThread();
t1.start();
t2.start(); }
}
class MyThread extends Thread {
public void run() {
for(int i = 1; i <= 1000; i++) {
System.out.println(getName() + "....." + i);
if(i % 10 == 0) {
Thread.yield(); //让出cpu,但是支持的不好
}
}
//t1.setPriority(Thread.MIN_PRIORITY); //设置最小优先级
//t2.setPriority(Thread.MAX_PRIORITY); //设置最大优先级
}
}
六.单例设计模式
单例设计模式:保证类在内存中只有一个对象。
如何保证类在内存中只有一个对象呢?
(1)控制类的创建,不让其他类来创建本类的对象。private
(2)在本类中定义一个本类的对象。Singleton s;
(3)提供公共的访问方式。 public static Singleton getInstance(){returns}
public class Demo3_Singleton {
/**
* @param args
* 单例设计模式(单个实例)
*/
public static void main(String[] args) {
Singleton s1 = Singleton.s;
//Singleton.s = null;
Singleton s2 = Singleton.s;
/*Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();*/
System.out.println(s1 == s2);
}
}
//饿汉式
class Singleton {
//1,私有构造函数
private Singleton(){}
//2,创建本类对象
private static Singleton s = new Singleton();
//3,对外提供公共的访问方法,返回是Singleton
public static Singleton getInstance() {
return s;
}
}
//懒汉式(单例的延迟加载)
class Singleton {
//1,私有构造函数
private Singleton(){}
//2,声明一个本类的引用
private static Singleton s ;
//3,对外提供公共的访问方法,返回是Singleton
public static Singleton getInstance() {
if(s == null)
s = new Singleton();
return s;
}
}
/*饿汉式和懒汉式的区别
* 1,饿汉式是空间换时间
* 懒汉式是时间换空间
* 2,多线程访问饿汉式不会创建多个实例
* 多线程访问懒汉式有可能会创建多个实例,所以多线程不能用懒汉式
*/
class Singleton {
//1,私有构造函数
private Singleton(){}
//2,创建本类对象
public final static Singleton s = new Singleton();
}
七.线程之间的通信
1.什么时候需要通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的
如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
2.怎么通信
如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程,就调用notify();
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
3.多个线程通信的问题
notify()方法是随机唤醒一个线程
notifyAll()方法是唤醒所有线程
JDK5之前无法唤醒指定的一个线程
如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
public class Demo7_NotifyAll {
/**
* @param args
*/
public static void main(String[] args) {
final Printer2 p = new Printer2();
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer2 {
private int flag = 1;
public void print1() throws InterruptedException {
synchronized(this){
while(flag != 1)
this.wait(); //线程等待
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
flag = 2; //flag改为2
this.notifyAll(); //唤醒等待的线程
}
}
public void print2() throws InterruptedException {
synchronized(this){
while(flag != 2) {
this.wait(); //线程等待,2线程等待
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
flag = 3; //flag改为3
this.notifyAll(); //唤醒等待的线程
}
}
public void print3() throws InterruptedException {
synchronized(this){
while(flag != 3) {
this.wait(); //线程等待,3线程等待
}
System.out.print("i");
System.out.print("t");
System.out.print("c");
System.out.print("a");
System.out.print("s");
System.out.print("t");
System.out.print("\r\n");
flag = 1; //flag改为1
this.notifyAll(); //唤醒等待的线程
}
}
}
/*
* sleep和wait的区别
* 1,sleep方法必须指定参数,wait方法可以指定参数也可以不指定,如果指定参数,根据参数的时间值之后等待,如果不指定参数直接等待
* 2,sleep方法在同步函数或者同步代码块中不释放锁对象,wait方法只能用在同步函数或同步代码块中,释放锁
*/
八.JDK5之后的线程控制
1.同步
使用ReentrantLock类的lock()和unlock()方法进行同步
2.通信
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition,这样就能区分唤醒的时候找哪个线程了
public class Demo8_ReentrantLock {
public static void main(String[] args) {
final Printer3 p = new Printer3();
new Thread() {
public void run() {
while(true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer3 {
private ReentrantLock r = new ReentrantLock(); //创建锁对象
private Condition c1 = r.newCondition(); //添加监视器
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() throws InterruptedException {
r.lock(); //获取锁
if(flag != 1)
c1.await(); //线程等待
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
flag = 2; //flag改为2
c2.signal(); //唤醒线程2
r.unlock(); //释放锁
}
public void print2() throws InterruptedException {
r.lock(); //获取锁
if(flag != 2) {
c2.await(); //线程等待,2线程等待
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
flag = 3; //flag改为3
c3.signal(); //唤醒线程3
r.unlock(); //释放锁
}
public void print3() throws InterruptedException {
r.lock(); //获取锁
if(flag != 3) {
c3.await(); //线程等待,3线程等待
}
System.out.print("i");
System.out.print("t");
System.out.print("c");
System.out.print("a");
System.out.print("s");
System.out.print("t");
System.out.print("\r\n");
flag = 1; //flag改为1
c1.signal(); //唤醒线程1
r.unlock(); //释放锁
}
}
九.GUI
1.事件处理
用户的一个操作就是一个事件, 事件处理就是在事件发生的时候程序做出什么反应
事件发生在哪个组件上, 哪个组件就是事件源
给事件源添加一个监听器对象
监听器对象中包含若干事件处理方法
如果事件发生了, 事件处理方法就会自动执行
2.适配器
a.什么是适配器
在使用监听器的时候, 需要定义一个类事件监听器接口.
通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.
适配器简化了这些操作, 我们定义监听器时只要继承适配器, 然后重写需要的方法即可.
b.适配器原理
适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.
目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.
* 适配器设计模式
public class Demo2_Adapter {
public static void main(String[] args) {
}
}
interface 和尚 {
public void 念经();
public void 打坐();
public void 撞钟();
public void 练武();
}
abstract class 天罡星 implements 和尚 {
@Override
public void 念经() {
}
@Override
public void 打坐() {
}
@Override
public void 撞钟() {
}
@Override
public void 练武() {
}
}
class 鲁智深 extends 天罡星 {
public void 练武() {
System.out.println("拳打镇关西");
System.out.println("大闹野猪林");
System.out.println("倒拔垂杨柳");
System.out.println("...");
}
}
事件处理
事件: 用户的一个操作
事件源: 被操作的组件
监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法
把监听器添加在事件源上,当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法
public class Demo3_Event {
/**
* @param args
*/
public static void main(String[] args) {
Frame f = new Frame(); //创建不可见窗体
f.setLocation(500, 50); //设置窗体的位置
f.setSize(400, 600); //设置窗体的宽和高
f.setTitle("GUI"); //设置窗体的标题
f.setLayout(new FlowLayout());//设置布局管理器为流式布局
Button b1 = new Button("按钮1");
b1.addMouseListener(new MouseAdapter() { //添加鼠标监听
@Override
public void mouseReleased(MouseEvent e) {//重写鼠标释放的方法
System.exit(0);
}
});
b1.addKeyListener(new KeyAdapter() { //添加键盘监听
@Override
public void keyReleased(KeyEvent e) { //键盘释放
if(e.getKeyCode() == KeyEvent.VK_ENTER) {//如果条件满足
System.exit(0); //退出jvm
}
}
});
Button b2 = new Button("按钮2");
b2.addActionListener(new ActionListener() { //添加动作监听
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
f.add(b1);
f.add(b2);
f.addWindowListener(new MyWindowAdapter()); //添加窗体监听
f.setVisible(true); //显示窗体
}
}
/*class MyMouseAdapter extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
System.exit(0);
}
@Override
public void mouseReleased(MouseEvent e) {
System.exit(0);
}
}*/